Commit all code. Should be mostly functional.
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/samber/lo"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/tools/blog/atom"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"opds-pse-server/app/comicinfo"
|
||||
"opds-pse-server/app/opds"
|
||||
"opds-pse-server/app/utils"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ctx *cli.Context
|
||||
var c *cache.Cache
|
||||
|
||||
func Init(cctx *cli.Context, r *mux.Router, cc *cache.Cache) {
|
||||
ctx = cctx
|
||||
c = cc
|
||||
r.HandleFunc("/", Index).Methods("GET")
|
||||
r.HandleFunc("/stream/{manga:.*}/{page:[0-9]+}", Stream).Methods("GET")
|
||||
}
|
||||
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
feed := opds.Feed{
|
||||
ID: "feed",
|
||||
Title: "go-opds-pse",
|
||||
Updated: atom.Time(time.Now()),
|
||||
Author: &opds.Author{
|
||||
Name: "go-opds-pse",
|
||||
URI: "https://github.com/sclark-dev/go-opds-pse",
|
||||
},
|
||||
Link: []opds.Link{
|
||||
{
|
||||
Rel: "start",
|
||||
Href: "/",
|
||||
Type: "application/atom+xml;profile=opds-catalog;kind=navigation",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
feed.Entry = append(feed.Entry)
|
||||
|
||||
_ = filepath.WalkDir(ctx.String("manga"), func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() || filepath.Ext(d.Name()) != ".cbz" {
|
||||
return err
|
||||
}
|
||||
|
||||
hrefPath := strings.TrimPrefix(path, ctx.String("manga"))
|
||||
|
||||
// Check if ComicInfo is cached
|
||||
var ComicInfo comicinfo.ComicInfo
|
||||
ci, found := c.Get(path)
|
||||
if found {
|
||||
ComicInfo = ci.(comicinfo.ComicInfo)
|
||||
} else {
|
||||
zf, _ := zip.OpenReader(path)
|
||||
for i := range zf.File {
|
||||
if strings.ToLower(zf.File[i].Name) == "comicinfo.xml" {
|
||||
cif, err := zf.File[i].Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cib, _ := io.ReadAll(cif)
|
||||
_ = cif.Close()
|
||||
_ = xml.Unmarshal(cib, &ComicInfo)
|
||||
c.Set(path, ComicInfo, cache.NoExpiration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entry := &opds.Entry{
|
||||
ID: utils.FileBasename(d.Name()),
|
||||
Title: utils.FileBasename(d.Name()),
|
||||
Author: &opds.Author{
|
||||
Name: ComicInfo.Writer,
|
||||
},
|
||||
Summary: &opds.Text{Body: ComicInfo.Summary},
|
||||
DcPublisher: ComicInfo.Publisher,
|
||||
Link: []opds.Link{
|
||||
{
|
||||
Rel: "http://opds-spec.org/acquisition",
|
||||
Href: "/manga" + hrefPath,
|
||||
Type: "application/x-cbz",
|
||||
Title: utils.FileBasename(d.Name()),
|
||||
},
|
||||
{
|
||||
Rel: "http://opds-spec.org/image",
|
||||
Href: "/stream" + hrefPath + "/0",
|
||||
Type: "image/png",
|
||||
},
|
||||
{
|
||||
Rel: "http://opds-spec.org/image/thumbnail",
|
||||
Href: "/stream" + hrefPath + "/0",
|
||||
Type: "image/png",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if ComicInfo != (comicinfo.ComicInfo{}) {
|
||||
entry.Link = append(entry.Link, opds.Link{
|
||||
Rel: "http://vaemendis.net/opds-pse/stream",
|
||||
Href: "/stream" + hrefPath + "/{pageNumber}",
|
||||
Type: "image/png",
|
||||
PseCount: uint(ComicInfo.PageCount),
|
||||
})
|
||||
}
|
||||
|
||||
feed.Entry = append(feed.Entry, entry)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = defaults.Set(&feed)
|
||||
data, _ := xml.MarshalIndent(feed, "", " ")
|
||||
http.ServeContent(w, r, "feed.xml", time.Now(), bytes.NewReader(data))
|
||||
}
|
||||
|
||||
func Stream(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse all variables
|
||||
vars := mux.Vars(r)
|
||||
path := ctx.String("manga") + "/" + vars["manga"]
|
||||
page, err := strconv.Atoi(vars["page"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Open zip file
|
||||
zf, err := zip.OpenReader(path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer zf.Close()
|
||||
|
||||
// Filter to just accepted image files
|
||||
pages := lo.Filter(zf.File, func(f *zip.File, i int) bool {
|
||||
if slices.Contains([]string{".png", ".jpg", ".webp"}, filepath.Ext(f.Name)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Check if page number exists
|
||||
if page > len(pages)-1 {
|
||||
http.Error(w, fmt.Sprintf("page %d out of range", page), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Sort pages lexically
|
||||
sort.Slice(pages, func(i, j int) bool {
|
||||
return pages[i].Name < pages[j].Name
|
||||
})
|
||||
|
||||
// Open page for reading
|
||||
imf, err := pages[page].Open()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer imf.Close()
|
||||
|
||||
// Read all data from page
|
||||
imageBytes, err := io.ReadAll(imf)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve page data
|
||||
http.ServeContent(w, r, pages[page].Name, time.Now(), bytes.NewReader(imageBytes))
|
||||
}
|
||||
Reference in New Issue
Block a user