diff --git a/convert/mobi.go b/convert/mobi.go index 13acbd9..511a4f1 100644 --- a/convert/mobi.go +++ b/convert/mobi.go @@ -2,7 +2,7 @@ package convert import ( "bytes" - "fmt" + "log/slog" "os/exec" "path/filepath" "strings" @@ -46,13 +46,21 @@ func (mc *MobiConverter) Convert(input string) (string, error) { cmd.Stderr = &stderr err := cmd.Run() if err != nil { + isError := true if exiterr, ok := err.(*exec.ExitError); ok { - if exiterr.ExitCode() != 1 { - fmt.Println(fmt.Sprint(err) + ": " + out.String() + ":" + stderr.String()) - return "", err + // Sometimes warnings cause a 1 exit-code, but the file is still created + slog.Info("Exit code", slog.Any("code", exiterr.ExitCode())) + if exiterr.ExitCode() == 1 { + isError = false } - } else { - fmt.Println(fmt.Sprint(err) + ": " + out.String() + ":" + stderr.String()) + } + + if isError { + slog.Error("Error converting file", + slog.Any("error", err), + slog.String("stdout", out.String()), + slog.String("stderr", stderr.String()), + ) return "", err } } diff --git a/go.mod b/go.mod index 006d517..0a78054 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,14 @@ require ( github.com/knadh/koanf/v2 v2.1.1 ) -require github.com/gorilla/securecookie v1.1.2 // indirect +require github.com/gorilla/securecookie v1.1.2 require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect - github.com/knadh/koanf/providers/basicflag v1.0.0 + github.com/knadh/koanf/providers/confmap v0.1.0 github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index 6a19494..4929c44 100644 --- a/go.sum +++ b/go.sum @@ -6,14 +6,16 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= -github.com/knadh/koanf/providers/basicflag v1.0.0 h1:qB0es/9fYsLuYnrKazxNCuWtkv3JFX1lI1druUsDDvY= -github.com/knadh/koanf/providers/basicflag v1.0.0/go.mod h1:n0NlnaxXUCER/WIzRroT9q3Np+FiZ9pSjrC6A/OozI8= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= github.com/knadh/koanf/providers/file v1.0.0 h1:DtPvSQBeF+N0QLPMz0yf2bx0nFSxUcncpqQvzCxfCyk= github.com/knadh/koanf/providers/file v1.0.0/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI= github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= @@ -24,8 +26,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/html/feed.go b/html/feed.go index 51b2970..bac9a99 100644 --- a/html/feed.go +++ b/html/feed.go @@ -92,6 +92,7 @@ func constructLink(url string, entry opds.Entry) LinkViewModel { return vm } + func resolveHref(feedUrl string, relativePath string) string { baseUrl, err := url.Parse(feedUrl) if err != nil { diff --git a/html/html.go b/html/html.go index 4ea0de4..22c3cfe 100644 --- a/html/html.go +++ b/html/html.go @@ -39,11 +39,8 @@ type LoginParams struct { ReturnURL string } -func Login(w io.Writer, p LoginParams, partial string) error { - if partial == "" { - partial = "layout.html" - } - return login.ExecuteTemplate(w, partial, p) +func Login(w io.Writer, p LoginParams) error { + return login.Execute(w, p) } type FeedParams struct { @@ -51,14 +48,9 @@ type FeedParams struct { Feed *opds.Feed } -func Feed(w io.Writer, p FeedParams, partial string) error { - if partial == "" { - partial = "layout.html" - } - +func Feed(w io.Writer, p FeedParams) error { vm := convertFeed(&p) - - return feed.ExecuteTemplate(w, partial, vm) + return feed.Execute(w, vm) } type FeedInfo struct { @@ -66,11 +58,8 @@ type FeedInfo struct { URL string } -func Home(w io.Writer, vm []FeedInfo, partial string) error { - if partial == "" { - partial = "layout.html" - } - return home.ExecuteTemplate(w, partial, vm) +func Home(w io.Writer, vm []FeedInfo) error { + return home.Execute(w, vm) } func StaticFiles() embed.FS { diff --git a/main.go b/main.go index 2cdd2d0..d282013 100644 --- a/main.go +++ b/main.go @@ -5,44 +5,41 @@ import ( "flag" "fmt" "log" + "net/http" "os" "github.com/gorilla/securecookie" "github.com/knadh/koanf/parsers/yaml" - "github.com/knadh/koanf/providers/basicflag" + "github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/v2" ) -type config struct { +type ProxyConfig struct { Port string `koanf:"port"` - Auth auth `koanf:"auth"` - Feeds []feedConfig `koanf:"feeds" ` + Auth AuthConfig `koanf:"auth"` + Feeds []FeedConfig `koanf:"feeds" ` } -type auth struct { +type AuthConfig struct { HashKey string `koanf:"hash_key"` BlockKey string `koanf:"block_key"` } -type feedConfig struct { +type FeedConfig struct { Name string `koanf:"name"` Url string `koanf:"url"` Username string `koanf:"username"` Password string `koanf:"password"` } -func (f feedConfig) HasCredentials() bool { - return f.Username != "" && f.Password != "" -} - -var k = koanf.New(".") - func main() { fs := flag.NewFlagSet("", flag.ContinueOnError) - fs.String("port", "8080", "port to listen on") + // These aren't mapped to the config file. configPath := fs.String("config", "config.yml", "config file to load") generateKeys := fs.Bool("generate-keys", false, "generate cookie signing keys and exit") + + port := fs.String("port", "8080", "port to listen on") if err := fs.Parse(os.Args[1:]); err != nil { log.Fatal(err) } @@ -52,18 +49,22 @@ func main() { os.Exit(0) } + var k = koanf.New(".") + // Load config file from disk. // Feed options must be defined here. if err := k.Load(file.Provider(*configPath), yaml.Parser()); err != nil && !os.IsNotExist(err) { log.Fatal(err) } - // Flags take precedence over config file. - if err := k.Load(basicflag.Provider(fs, "."), nil); err != nil { + // Selectively add command line options to the config. Overriding the config file. + if err := k.Load(confmap.Provider(map[string]interface{}{ + "port": *port, + }, "."), nil); err != nil { log.Fatal(err) } - config := config{} + config := ProxyConfig{} k.Unmarshal("", &config) if len(config.Feeds) == 0 { @@ -82,7 +83,10 @@ func main() { if err != nil { log.Fatal(err) } - server.Serve() + + if err = server.Serve(); err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } } func displayKeys() (string, string) { diff --git a/server.go b/server.go index 5660a8f..eda3026 100644 --- a/server.go +++ b/server.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "fmt" "io" - "log" "log/slog" "mime" "net/http" @@ -43,7 +42,7 @@ type Credentials struct { Password string } -func NewServer(config *config) (*Server, error) { +func NewServer(config *ProxyConfig) (*Server, error) { hashKey, err := hex.DecodeString(config.Auth.HashKey) if err != nil { return nil, err @@ -68,12 +67,12 @@ func NewServer(config *config) (*Server, error) { }, nil } -func (s *Server) Serve() { +func (s *Server) Serve() error { slog.Info("Starting server", slog.String("port", s.addr)) - log.Fatal(http.ListenAndServe(s.addr, s.router)) + return http.ListenAndServe(s.addr, s.router) } -func handleHome(feeds []feedConfig) http.HandlerFunc { +func handleHome(feeds []FeedConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vmFeeds := make([]html.FeedInfo, len(feeds)) for i, feed := range feeds { @@ -83,11 +82,11 @@ func handleHome(feeds []feedConfig) http.HandlerFunc { } } - html.Home(w, vmFeeds, partial(r)) + html.Home(w, vmFeeds) } } -func handleFeed(outputDir string, feeds []feedConfig, s *securecookie.SecureCookie) http.HandlerFunc { +func handleFeed(outputDir string, feeds []FeedConfig, s *securecookie.SecureCookie) http.HandlerFunc { kepubConverter := &convert.KepubConverter{} mobiConverter := &convert.MobiConverter{} @@ -107,7 +106,7 @@ func handleFeed(outputDir string, feeds []feedConfig, s *securecookie.SecureCook searchTerm := r.URL.Query().Get("search") if searchTerm != "" { - queryURL = replaceSearchPlaceHolder(queryURL, searchTerm) + queryURL = strings.Replace(queryURL, "{searchTerms}", searchTerm, 1) } resp, err := fetchFromUrl(queryURL, getCredentials(r, feeds, s)) @@ -122,8 +121,7 @@ func handleFeed(outputDir string, feeds []feedConfig, s *securecookie.SecureCook return } - contentType := resp.Header.Get("Content-Type") - mimeType, _, err := mime.ParseMediaType(contentType) + mimeType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) if err != nil { handleError(r, w, "Failed to parse content type", err) } @@ -140,7 +138,7 @@ func handleFeed(outputDir string, feeds []feedConfig, s *securecookie.SecureCook Feed: feed, } - if err = html.Feed(w, feedParams, partial(r)); err != nil { + if err = html.Feed(w, feedParams); err != nil { handleError(r, w, "Failed to render feed", err) return } @@ -187,7 +185,7 @@ func handleAuth(s *securecookie.SecureCookie) http.HandlerFunc { } if r.Method == "GET" { - html.Login(w, html.LoginParams{ReturnURL: returnUrl}, partial(r)) + html.Login(w, html.LoginParams{ReturnURL: returnUrl}) return } @@ -231,7 +229,7 @@ func handleAuth(s *securecookie.SecureCookie) http.HandlerFunc { } } -func getCredentials(r *http.Request, feeds []feedConfig, s *securecookie.SecureCookie) *Credentials { +func getCredentials(r *http.Request, feeds []FeedConfig, s *securecookie.SecureCookie) *Credentials { if !r.URL.Query().Has("q") { return nil } @@ -243,7 +241,7 @@ func getCredentials(r *http.Request, feeds []feedConfig, s *securecookie.SecureC // Try to get credentials from the config first for _, feed := range feeds { - if !feed.HasCredentials() { + if feed.Username == "" || feed.Password == "" { continue } @@ -293,14 +291,6 @@ func handleError(r *http.Request, w http.ResponseWriter, message string, err err http.Error(w, "An unexpected error occurred", http.StatusInternalServerError) } -func replaceSearchPlaceHolder(url string, searchTerm string) string { - return strings.Replace(url, "{searchTerms}", searchTerm, 1) -} - -func partial(req *http.Request) string { - return req.URL.Query().Get("partial") -} - func downloadFile(path string, resp *http.Response) error { file, err := os.Create(path) if err != nil {