Support localized feed errors generated by background workers
This commit is contained in:
parent
9694861cb6
commit
953d0a2dc0
18 changed files with 59 additions and 65 deletions
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/config"
|
||||
"github.com/miniflux/miniflux/locale"
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/reader/feed"
|
||||
"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, syscall.SIGTERM)
|
||||
|
||||
feedHandler := feed.NewFeedHandler(store)
|
||||
translator := locale.Load()
|
||||
feedHandler := feed.NewFeedHandler(store, translator)
|
||||
pool := scheduler.NewWorkerPool(feedHandler, cfg.WorkerPoolSize())
|
||||
server := newServer(cfg, store, pool, feedHandler)
|
||||
server := newServer(cfg, store, pool, feedHandler, translator)
|
||||
|
||||
scheduler.NewFeedScheduler(
|
||||
store,
|
||||
|
|
|
@ -23,9 +23,8 @@ import (
|
|||
"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()
|
||||
translator := locale.Load()
|
||||
templateEngine := template.NewEngine(cfg, router, translator)
|
||||
|
||||
apiController := api.NewController(store, feedHandler)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/config"
|
||||
"github.com/miniflux/miniflux/locale"
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/reader/feed"
|
||||
"github.com/miniflux/miniflux/scheduler"
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
"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()
|
||||
keyFile := cfg.KeyFile()
|
||||
certDomain := cfg.CertDomain()
|
||||
|
@ -28,7 +29,7 @@ func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.Worke
|
|||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
Addr: cfg.ListenAddr(),
|
||||
Handler: routes(cfg, store, feedHandler, pool),
|
||||
Handler: routes(cfg, store, feedHandler, pool, translator),
|
||||
}
|
||||
|
||||
if certDomain != "" && certCache != "" {
|
||||
|
|
|
@ -27,6 +27,6 @@ func (l LocalizedError) Localize(translation *locale.Language) string {
|
|||
}
|
||||
|
||||
// NewLocalizedError returns a new LocalizedError.
|
||||
func NewLocalizedError(message string, args ...interface{}) LocalizedError {
|
||||
return LocalizedError{message: message, args: args}
|
||||
func NewLocalizedError(message string, args ...interface{}) *LocalizedError {
|
||||
return &LocalizedError{message: message, args: args}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
decoder := xml.NewDecoder(data)
|
||||
decoder.CharsetReader = encoding.CharsetReader
|
||||
|
|
|
@ -8,8 +8,6 @@ import (
|
|||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
)
|
||||
|
||||
func TestParseAtomSample(t *testing.T) {
|
||||
|
@ -430,8 +428,4 @@ func TestParseInvalidXml(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Parse should returns an error")
|
||||
}
|
||||
|
||||
if _, ok := err.(errors.LocalizedError); !ok {
|
||||
t.Error("The error returned must be a LocalizedError")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
"github.com/miniflux/miniflux/http"
|
||||
"github.com/miniflux/miniflux/locale"
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/reader/icon"
|
||||
|
@ -30,7 +31,8 @@ var (
|
|||
|
||||
// Handler contains all the logic to create and refresh feeds.
|
||||
type Handler struct {
|
||||
store *storage.Storage
|
||||
store *storage.Storage
|
||||
translator *locale.Translator
|
||||
}
|
||||
|
||||
// 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)
|
||||
response, err := client.Get()
|
||||
if err != nil {
|
||||
if _, ok := err.(errors.LocalizedError); ok {
|
||||
if _, ok := err.(*errors.LocalizedError); ok {
|
||||
return nil, 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)
|
||||
}
|
||||
|
||||
subscription, err := parseFeed(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
subscription, feedErr := parseFeed(body)
|
||||
if feedErr != nil {
|
||||
return nil, feedErr
|
||||
}
|
||||
|
||||
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.
|
||||
func (h *Handler) RefreshFeed(userID, feedID int64) error {
|
||||
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)
|
||||
if err != nil {
|
||||
|
@ -124,14 +133,14 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
|
|||
response, err := client.Get()
|
||||
if err != nil {
|
||||
var customErr errors.LocalizedError
|
||||
if lerr, ok := err.(errors.LocalizedError); ok {
|
||||
customErr = lerr
|
||||
if lerr, ok := err.(*errors.LocalizedError); ok {
|
||||
customErr = *lerr
|
||||
} else {
|
||||
customErr = errors.NewLocalizedError(errRequestFailed, err)
|
||||
customErr = *errors.NewLocalizedError(errRequestFailed, err)
|
||||
}
|
||||
|
||||
originalFeed.ParsingErrorCount++
|
||||
originalFeed.ParsingErrorMsg = customErr.Error()
|
||||
originalFeed.ParsingErrorMsg = customErr.Localize(currentLanguage)
|
||||
h.store.UpdateFeed(originalFeed)
|
||||
return customErr
|
||||
}
|
||||
|
@ -141,7 +150,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
|
|||
if response.HasServerFailure() {
|
||||
err := errors.NewLocalizedError(errServerFailure, response.StatusCode)
|
||||
originalFeed.ParsingErrorCount++
|
||||
originalFeed.ParsingErrorMsg = err.Error()
|
||||
originalFeed.ParsingErrorMsg = err.Localize(currentLanguage)
|
||||
h.store.UpdateFeed(originalFeed)
|
||||
return err
|
||||
}
|
||||
|
@ -153,7 +162,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
|
|||
if response.ContentLength == 0 {
|
||||
err := errors.NewLocalizedError(errEmptyFeed)
|
||||
originalFeed.ParsingErrorCount++
|
||||
originalFeed.ParsingErrorMsg = err.Error()
|
||||
originalFeed.ParsingErrorMsg = err.Localize(currentLanguage)
|
||||
h.store.UpdateFeed(originalFeed)
|
||||
return err
|
||||
}
|
||||
|
@ -163,10 +172,10 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
|
|||
return errors.NewLocalizedError(errEncoding, err)
|
||||
}
|
||||
|
||||
subscription, err := parseFeed(body)
|
||||
if err != nil {
|
||||
subscription, parseErr := parseFeed(body)
|
||||
if parseErr != nil {
|
||||
originalFeed.ParsingErrorCount++
|
||||
originalFeed.ParsingErrorMsg = err.Error()
|
||||
originalFeed.ParsingErrorMsg = parseErr.Localize(currentLanguage)
|
||||
h.store.UpdateFeed(originalFeed)
|
||||
return err
|
||||
}
|
||||
|
@ -209,6 +218,6 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
|
|||
}
|
||||
|
||||
// NewFeedHandler returns a feed handler.
|
||||
func NewFeedHandler(store *storage.Storage) *Handler {
|
||||
return &Handler{store: store}
|
||||
func NewFeedHandler(store *storage.Storage, translator *locale.Translator) *Handler {
|
||||
return &Handler{store, translator}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ package feed
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/reader/atom"
|
||||
|
@ -66,13 +66,13 @@ func DetectFeedFormat(r io.Reader) string {
|
|||
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]")
|
||||
|
||||
var buffer bytes.Buffer
|
||||
size, _ := io.Copy(&buffer, r)
|
||||
if size == 0 {
|
||||
return nil, errors.New("This feed is empty")
|
||||
return nil, errors.NewLocalizedError("This feed is empty")
|
||||
}
|
||||
|
||||
str := stripInvalidXMLCharacters(buffer.String())
|
||||
|
@ -90,7 +90,7 @@ func parseFeed(r io.Reader) (*model.Feed, error) {
|
|||
case FormatRDF:
|
||||
return rdf.Parse(reader)
|
||||
default:
|
||||
return nil, errors.New("Unsupported feed format")
|
||||
return nil, errors.NewLocalizedError("Unsupported feed format")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
decoder := json.NewDecoder(data)
|
||||
if err := decoder.Decode(&feed); err != nil {
|
||||
|
|
|
@ -9,8 +9,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
)
|
||||
|
||||
func TestParseJsonFeed(t *testing.T) {
|
||||
|
@ -377,8 +375,4 @@ func TestParseInvalidJSON(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Parse should returns an error")
|
||||
}
|
||||
|
||||
if _, ok := err.(errors.LocalizedError); !ok {
|
||||
t.Error("The error returned must be a LocalizedError")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
decoder := xml.NewDecoder(data)
|
||||
decoder.CharsetReader = encoding.CharsetReader
|
||||
|
|
|
@ -7,8 +7,6 @@ package opml
|
|||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
)
|
||||
|
||||
func TestParseOpmlWithoutCategories(t *testing.T) {
|
||||
|
@ -130,8 +128,4 @@ func TestParseInvalidXML(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Parse should generate an error")
|
||||
}
|
||||
|
||||
if _, ok := err.(errors.LocalizedError); !ok {
|
||||
t.Error("The error returned must be a LocalizedError")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
decoder := xml.NewDecoder(data)
|
||||
decoder.CharsetReader = encoding.CharsetReader
|
||||
|
|
|
@ -9,8 +9,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
)
|
||||
|
||||
func TestParseRDFSample(t *testing.T) {
|
||||
|
@ -330,8 +328,4 @@ func TestParseInvalidXml(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Parse should returns an error")
|
||||
}
|
||||
|
||||
if _, ok := err.(errors.LocalizedError); !ok {
|
||||
t.Error("The error returned must be a LocalizedError")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
decoder := xml.NewDecoder(data)
|
||||
decoder.CharsetReader = encoding.CharsetReader
|
||||
|
|
|
@ -8,8 +8,6 @@ import (
|
|||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/errors"
|
||||
)
|
||||
|
||||
func TestParseRss2Sample(t *testing.T) {
|
||||
|
@ -564,8 +562,4 @@ func TestParseInvalidXml(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Parse should returns an error")
|
||||
}
|
||||
|
||||
if _, ok := err.(errors.LocalizedError); !ok {
|
||||
t.Error("The error returned must be a LocalizedError")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,6 +175,18 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
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.
|
||||
func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
||||
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByID] userID=%d", userID))
|
||||
|
|
|
@ -85,8 +85,9 @@ func (f *funcMap) Map() template.FuncMap {
|
|||
case string:
|
||||
return f.Language.Get(key.(string), args...)
|
||||
case errors.LocalizedError:
|
||||
err := key.(errors.LocalizedError)
|
||||
return err.Localize(f.Language)
|
||||
return key.(errors.LocalizedError).Localize(f.Language)
|
||||
case *errors.LocalizedError:
|
||||
return key.(*errors.LocalizedError).Localize(f.Language)
|
||||
case error:
|
||||
return key.(error).Error()
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue