Merge pull request #13 from dubyte/time_calculated_once
Time calculated once and xml mime type
This commit is contained in:
commit
f5280d4bfb
@ -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.
|
||||||
|
@ -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"
|
||||||
|
case pathTypeDirOfDirs:
|
||||||
|
return "application/atom+xml;profile=opds-catalog;kind=navigation"
|
||||||
|
default:
|
||||||
|
return mime.TypeByExtension("xml")
|
||||||
}
|
}
|
||||||
return "application/atom+xml;profile=opds-catalog;kind=acquisition"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user