From ce288cef9e85da18900f9c159040c8c545e1f8ab Mon Sep 17 00:00:00 2001 From: Sinuhe Tellez Date: Fri, 3 Mar 2017 22:41:48 -0500 Subject: [PATCH] Moving dir2opds from opds repo --- README.md | 14 +++++ main.go | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 main.go diff --git a/README.md b/README.md index d116f9f..a5f9cf7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # dir2opds Serve an OPDS server based on a directory + +Some times you only have a directory with books and you want an OPDS, but to get from dir to OPDS you maybe need to do some work, this project aim to +simplely works. + +It didn't pass the opds validator yet. But the plan is to get to that point. + +# Installation + +go get github.com/dubyte/dir2opds + +#usage + +dir2opds -dir ./books -port 8080 + diff --git a/main.go b/main.go new file mode 100644 index 0000000..503ffb6 --- /dev/null +++ b/main.go @@ -0,0 +1,157 @@ +/* + Copyright (C) 2017 Sinuhé Téllez Rivera + + dir2opds is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + dir2opds is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with dir2opds. If not, see . +*/ + +package main + +import ( + "encoding/xml" + "encoding/base64" + "flag" + "io" + "io/ioutil" + "log" + "mime" + "net/http" + "os" + "path" + "path/filepath" + + "golang.org/x/tools/blog/atom" +) + +var dirRoot string + +func init() { + mime.AddExtensionType(".mobi", "application/x-mobipocket-ebook") + mime.AddExtensionType(".epub", "application/epub+zip") + mime.AddExtensionType(".fb2", "txt/xml") + http.HandleFunc("/", errorHandler(catalogRoot)) +} + +func main() { + portPtr := flag.String("port", "8080", "The server will listen in this port") + dirPtr := flag.String("dir", "./books", "A directory with books") + flag.Parse() + + dirRoot = *dirPtr + log.Fatal(http.ListenAndServe(":"+*portPtr, nil)) +} + +func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + err := f(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Printf("handling %q: %v", r.RequestURI, err) + } + } +} + +func catalogRoot(w http.ResponseWriter, req *http.Request) error { + dirPath := filepath.Join(dirRoot, req.URL.Path) + fi, err := os.Stat(dirPath) + if err != nil { + return err + } + + if fi.IsDir() { + return catalogFeed(w, req, dirPath) + } + return writeFileTo(w, dirPath) +} + +func catalogFeed(w io.Writer, r *http.Request, dirPath string) error { + fis, err := ioutil.ReadDir(dirPath) + if err != nil { + return err + } + feed := &atom.Feed{Title: "OPDS Catalog: " + r.URL.Path} + if len(fis) < 1 { + return writeFeedTo(w, feed) + } + + err = FeedEntries(feed, fis, r) + if err != nil { + return err + } + + return writeFeedTo(w, feed) +} + +func FeedEntries(f *atom.Feed, fis []os.FileInfo, r *http.Request) error { + for _, fi := range fis { + e := &atom.Entry{Title: fi.Name()} + encoded := base64.StdEncoding.EncodeToString([]byte(path.Join(r.URL.Path, fi.Name()))) + e.ID = encoded + l := atom.Link{Title: fi.Name(), Href: path.Join(r.URL.EscapedPath(), fi.Name())} + if !fi.IsDir() { + l.Rel = "http://opds-spec.org/acquisition" + } + lType, err := getLinkType(path.Join(dirRoot, r.URL.Path, fi.Name())) + if err != nil { + return err + } + l.Type = lType + e.Link = append(e.Link, l) + f.Entry = append(f.Entry, e) + } + return nil +} + +func writeFeedTo(w io.Writer, feed *atom.Feed) error { + io.WriteString(w, "") + enc := xml.NewEncoder(w) + enc.Indent(" ", " ") + if err := enc.Encode(feed); err != nil { + return err + } + return nil +} + +func writeFileTo(w io.Writer, filepath string) error { + f, err := os.Open(filepath) + if err != nil { + return err + } + _, err = io.Copy(w, f) + if err != nil { + f.Close() + return err + } + f.Close() + return nil +} + +func getLinkType(lPath string) (string, error) { + fi, err := os.Stat(lPath) + if err != nil { + return "", err + } + if !fi.IsDir() { + return mime.TypeByExtension(filepath.Ext(lPath)), nil + } + files, err := ioutil.ReadDir(lPath) + if err != nil { + return "", err + } + for _, file := range files { + if !file.IsDir() { + return "application/atom+xml;profile=opds-catalog;kind=acquisition", nil + } + } + return "application/atom+xml;profile=opds-catalog;kind=navigation", nil +}