Merge pull request #13 from dubyte/time_calculated_once

Time calculated once and xml mime type
This commit is contained in:
Sinuhe Tellez Rivera 2021-06-10 02:56:40 -04:00 committed by GitHub
commit f5280d4bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 43 deletions

View File

@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2021-06-10
### Changed
- time is calculated only once
- updated the mimetype returned for the navigation and acquisition xmls
## [0.0.11] - 2021-06-05 ## [0.0.11] - 2021-06-05
### Changed ### Changed
- return to filepath as the best way to handle paths for different platforms. - return to filepath as the best way to handle paths for different platforms.

View File

@ -27,6 +27,12 @@ func init() {
_ = mime.AddExtensionType(".fb2", "text/fb2+xml") _ = mime.AddExtensionType(".fb2", "text/fb2+xml")
} }
const (
pathTypeFile = iota
pathTypeDirOfDirs
pathTypeDirOfFiles
)
type OPDS struct { type OPDS struct {
DirRoot string DirRoot string
Author string Author string
@ -34,42 +40,42 @@ type OPDS struct {
AuthorURI string AuthorURI string
} }
var TimeNowFunc = timeNow var TimeNow = timeNowFunc()
// Handler serve the content of a book file or
// returns an Acquisition Feed when the entries are documents or
// returns an Navegation Feed when the entries are other folders
func (s OPDS) Handler(w http.ResponseWriter, req *http.Request) error { func (s OPDS) Handler(w http.ResponseWriter, req *http.Request) error {
fPath := filepath.Join(s.DirRoot, req.URL.Path) fPath := filepath.Join(s.DirRoot, req.URL.Path)
log.Printf("fPath:'%s'", fPath) log.Printf("fPath:'%s'", fPath)
fi, err := os.Stat(fPath) if getPathType(fPath) == pathTypeFile {
if err != nil {
return err
}
if isFile(fi) {
http.ServeFile(w, req, fPath) http.ServeFile(w, req, fPath)
return nil return nil
} }
content, err := s.getContent(req, fPath) navFeed := s.makeFeed(fPath, req)
var content []byte
var err error
if getPathType(fPath) == pathTypeDirOfFiles {
acFeed := &opds.AcquisitionFeed{Feed: &navFeed, Dc: "http://purl.org/dc/terms/", Opds: "http://opds-spec.org/2010/catalog"}
content, err = xml.MarshalIndent(acFeed, " ", " ")
w.Header().Add("Content-Type", "application/atom+xml;profile=opds-catalog;kind=acquisition")
} else {
content, err = xml.MarshalIndent(navFeed, " ", " ")
w.Header().Add("Content-Type", "application/atom+xml;profile=opds-catalog;kind=navigation")
}
if err != nil { if err != nil {
log.Printf("error while serving '%s': %s", fPath, err)
return err return err
} }
content = append([]byte(xml.Header), content...) content = append([]byte(xml.Header), content...)
http.ServeContent(w, req, "feed.xml", TimeNowFunc(), bytes.NewReader(content)) http.ServeContent(w, req, "feed.xml", TimeNow(), bytes.NewReader(content))
return nil
}
func (s OPDS) getContent(req *http.Request, dirpath string) (result []byte, err error) { return nil
feed := s.makeFeed(dirpath, req)
if getPathType(dirpath) == pathTypeDirOfFiles {
acFeed := &opds.AcquisitionFeed{&feed, "http://purl.org/dc/terms/", "http://opds-spec.org/2010/catalog"}
result, err = xml.MarshalIndent(acFeed, " ", " ")
} else {
result, err = xml.MarshalIndent(feed, " ", " ")
}
return
} }
const navigationType = "application/atom+xml;profile=opds-catalog;kind=navigation" const navigationType = "application/atom+xml;profile=opds-catalog;kind=navigation"
@ -79,7 +85,7 @@ func (s OPDS) makeFeed(dirpath string, req *http.Request) atom.Feed {
ID(req.URL.Path). ID(req.URL.Path).
Title("Catalog in " + req.URL.Path). Title("Catalog in " + req.URL.Path).
Author(opds.AuthorBuilder.Name(s.Author).Email(s.AuthorEmail).URI(s.AuthorURI).Build()). Author(opds.AuthorBuilder.Name(s.Author).Email(s.AuthorEmail).URI(s.AuthorURI).Build()).
Updated(TimeNowFunc()). Updated(TimeNow()).
AddLink(opds.LinkBuilder.Rel("start").Href("/").Type(navigationType).Build()) AddLink(opds.LinkBuilder.Rel("start").Href("/").Type(navigationType).Build())
fis, _ := ioutil.ReadDir(dirpath) fis, _ := ioutil.ReadDir(dirpath)
@ -89,12 +95,12 @@ func (s OPDS) makeFeed(dirpath string, req *http.Request) atom.Feed {
AddEntry(opds.EntryBuilder. AddEntry(opds.EntryBuilder.
ID(req.URL.Path + fi.Name()). ID(req.URL.Path + fi.Name()).
Title(fi.Name()). Title(fi.Name()).
Updated(TimeNowFunc()). Updated(TimeNow()).
Published(TimeNowFunc()). Published(TimeNow()).
AddLink(opds.LinkBuilder. AddLink(opds.LinkBuilder.
Rel(getRel(fi.Name(), pathType)). Rel(getRel(fi.Name(), pathType)).
Title(fi.Name()). Title(fi.Name()).
Href(getHref(req, fi.Name())). Href(filepath.Join(req.URL.RequestURI(), url.PathEscape(fi.Name()))).
Type(getType(fi.Name(), pathType)). Type(getType(fi.Name(), pathType)).
Build()). Build()).
Build()) Build())
@ -117,22 +123,18 @@ func getRel(name string, pathType int) string {
} }
func getType(name string, pathType int) string { func getType(name string, pathType int) string {
if pathType == pathTypeFile { switch pathType {
case pathTypeFile:
return mime.TypeByExtension(filepath.Ext(name)) return mime.TypeByExtension(filepath.Ext(name))
} case pathTypeDirOfFiles:
return "application/atom+xml;profile=opds-catalog;kind=acquisition" return "application/atom+xml;profile=opds-catalog;kind=acquisition"
case pathTypeDirOfDirs:
return "application/atom+xml;profile=opds-catalog;kind=navigation"
default:
return mime.TypeByExtension("xml")
} }
func getHref(req *http.Request, name string) string {
return filepath.Join(req.URL.RequestURI(), url.PathEscape(name))
} }
const (
pathTypeFile = iota
pathTypeDirOfDirs
pathTypeDirOfFiles
)
func getPathType(dirpath string) int { func getPathType(dirpath string) int {
fi, _ := os.Stat(dirpath) fi, _ := os.Stat(dirpath)
if isFile(fi) { if isFile(fi) {
@ -153,6 +155,7 @@ func isFile(fi os.FileInfo) bool {
return !fi.IsDir() return !fi.IsDir()
} }
func timeNow() time.Time { func timeNowFunc() func() time.Time {
return time.Now() t := time.Now()
return func() time.Time { return t }
} }

View File

@ -14,9 +14,9 @@ import (
func TestHandler(t *testing.T) { func TestHandler(t *testing.T) {
// pre-setup // pre-setup
nowFn := service.TimeNowFunc nowFn := service.TimeNow
defer func() { defer func() {
service.TimeNowFunc = nowFn service.TimeNow = nowFn
}() }()
tests := map[string]struct { tests := map[string]struct {
@ -24,8 +24,8 @@ func TestHandler(t *testing.T) {
want string want string
WantedContentType string WantedContentType string
}{ }{
"feed (dir of folders )": {input: "/", want: feed, WantedContentType: "application/xml"}, "feed (dir of dirs )": {input: "/", want: feed, WantedContentType: "application/atom+xml;profile=opds-catalog;kind=navigation"},
"acquisitionFeed(dir of files)": {input: "/mybook", want: acquisitionFeed, WantedContentType: "application/xml"}, "acquisitionFeed(dir of files)": {input: "/mybook", want: acquisitionFeed, WantedContentType: "application/atom+xml;profile=opds-catalog;kind=acquisition"},
"servingAFile": {input: "/mybook/mybook.txt", want: "Fixture", WantedContentType: "text/plain; charset=utf-8"}, "servingAFile": {input: "/mybook/mybook.txt", want: "Fixture", WantedContentType: "text/plain; charset=utf-8"},
"serving file with spaces": {input: "/mybook/mybook%20copy.txt", want: "Fixture", WantedContentType: "text/plain; charset=utf-8"}, "serving file with spaces": {input: "/mybook/mybook%20copy.txt", want: "Fixture", WantedContentType: "text/plain; charset=utf-8"},
} }
@ -36,7 +36,7 @@ func TestHandler(t *testing.T) {
s := service.OPDS{"testdata", "", "", ""} s := service.OPDS{"testdata", "", "", ""}
w := httptest.NewRecorder() w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, tc.input, nil) req := httptest.NewRequest(http.MethodGet, tc.input, nil)
service.TimeNowFunc = func() time.Time { service.TimeNow = func() time.Time {
return time.Date(2020, 05, 25, 00, 00, 00, 0, time.UTC) return time.Date(2020, 05, 25, 00, 00, 00, 0, time.UTC)
} }