Sfoglia il codice sorgente

Support localized feed errors generated by background workers

Frédéric Guillot 8 anni fa
parent
commit
953d0a2dc0

+ 4 - 2
daemon/daemon.go

@@ -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,

+ 1 - 2
daemon/routes.go

@@ -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)

+ 3 - 2
daemon/server.go

@@ -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 != "" {

+ 2 - 2
errors/errors.go

@@ -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}
 }

+ 1 - 1
reader/atom/parser.go

@@ -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

+ 0 - 6
reader/atom/parser_test.go

@@ -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")
-	}
 }

+ 25 - 16
reader/feed/handler.go

@@ -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}
 }

+ 4 - 4
reader/feed/parser.go

@@ -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")
 	}
 }
 

+ 1 - 1
reader/json/parser.go

@@ -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 {

+ 0 - 6
reader/json/parser_test.go

@@ -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")
-	}
 }

+ 1 - 1
reader/opml/parser.go

@@ -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

+ 0 - 6
reader/opml/parser_test.go

@@ -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")
-	}
 }

+ 1 - 1
reader/rdf/parser.go

@@ -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

+ 0 - 6
reader/rdf/parser_test.go

@@ -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")
-	}
 }

+ 1 - 1
reader/rss/parser.go

@@ -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

+ 0 - 6
reader/rss/parser_test.go

@@ -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")
-	}
 }

+ 12 - 0
storage/user.go

@@ -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))

+ 3 - 2
template/functions.go

@@ -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: