Basic OPDS support

This commit is contained in:
Las Zenow 2015-01-17 02:19:14 -06:00
parent 078b210392
commit b0a0b68561
6 changed files with 175 additions and 0 deletions

View file

@ -34,6 +34,7 @@ const (
IMG_PATH = "img/" IMG_PATH = "img/"
ROBOTS_PATH = "robots.txt" ROBOTS_PATH = "robots.txt"
DESCRIPTION_PATH = "description.json" DESCRIPTION_PATH = "description.json"
OPENSEARCH_PATH = "opensearch.xml"
LOGGER_CONFIG = "logger.xml" LOGGER_CONFIG = "logger.xml"
IMG_WIDTH_BIG = 300 IMG_WIDTH_BIG = 300

11
opensearch.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Imperial Library of Trantor</ShortName>
<Description>Book search in the library.</Description>
<InputEncoding>UTF-8</InputEncoding>
<OutputEncoding>UTF-8</OutputEncoding>
<Tags>books epub</Tags>
<Image type="image/x-icon" width="16" height="16">/img/favicon.ico</Image>
<Contact>zenow@riseup.net</Contact>
<Url type="application/atom+xml" template="/search/?fmt=opds&amp;q={searchTerms}"/>
</OpenSearchDescription>

View file

@ -9,6 +9,7 @@ import (
"errors" "errors"
"html/template" "html/template"
"net/http" "net/http"
"time"
"git.gitorious.org/trantor/trantor.git/database" "git.gitorious.org/trantor/trantor.git/database"
) )
@ -20,6 +21,7 @@ type Status struct {
User string User string
IsAdmin bool IsAdmin bool
Notif []Notification Notif []Notification
Updated string
Home bool Home bool
About bool About bool
News bool News bool
@ -36,6 +38,7 @@ func GetStatus(h handler) Status {
s.User = h.sess.User s.User = h.sess.User
s.IsAdmin = h.sess.IsAdmin() s.IsAdmin = h.sess.IsAdmin()
s.Notif = h.sess.GetNotif() s.Notif = h.sess.GetNotif()
s.Updated = time.Now().UTC().Format("2006-01-02T15:04:05Z")
h.sess.Save(h.w, h.r) h.sess.Save(h.w, h.r)
return s return s
} }
@ -66,12 +69,19 @@ var tmpl_rss = txt_tmpl.Must(txt_tmpl.ParseFiles(
TEMPLATE_PATH+"news.rss", TEMPLATE_PATH+"news.rss",
)) ))
var tmpl_opds = txt_tmpl.Must(txt_tmpl.ParseFiles(
TEMPLATE_PATH+"index.opds",
TEMPLATE_PATH+"search.opds",
))
func loadTemplate(h handler, tmpl string, data interface{}) { func loadTemplate(h handler, tmpl string, data interface{}) {
var err error var err error
fmt := h.r.FormValue("fmt") fmt := h.r.FormValue("fmt")
switch fmt { switch fmt {
case "rss": case "rss":
err = tmpl_rss.ExecuteTemplate(h.w, tmpl+".rss", data) err = tmpl_rss.ExecuteTemplate(h.w, tmpl+".rss", data)
case "opds":
err = tmpl_opds.ExecuteTemplate(h.w, tmpl+".opds", data)
case "json": case "json":
err = loadJson(h.w, tmpl, data) err = loadJson(h.w, tmpl, data)
default: default:

52
templates/index.opds Normal file
View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:odl="http://opds-spec.org/odl"
xml:lang="en"
xmlns="http://www.w3.org/2005/Atom"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:app="http://www.w3.org/2007/app"
xmlns:opds="http://opds-spec.org/2010/catalog"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
<id>{{.S.BaseURL}}</id>
<link rel="self"
href="/?fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="start"
href="/?fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="search"
href="/opensearch.xml"
type="application/opensearchdescription+xml"/>
<title>The Imperial Libary of Trantor</title>
<author>
<name>The Imperial Library of Trantor</name>
<uri>{{.S.BaseURL}}</uri>
<email>zenow@riseup.net</email>
</author>
<updated>{{.S.Updated}}</updated>
<icon>{{.S.BaseURL}}/img/favicon.ico</icon>
<entry>
<title>Last books added</title>
<link rel="http://opds-spec.org/sort/new"
href="/search/?fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>{{.S.Updated}}</updated>
<id>{{.S.BaseURL}}/search/</id>
</entry>
{{$updated := .S.Updated}}
{{$baseurl := .S.BaseURL}}
{{range .Tags}}
<entry>
<title>{{html .}}</title>
<link rel="http://opds-spec.org/facet"
href="/search/?q=subject:{{urlquery .}}&amp;fmt=opds"
title="{{html .}}"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>{{$updated}}</updated>
<id>{{$baseurl}}/search/?subject:{{urlquery .}}</id>
</entry>
{{end}}
</feed>

100
templates/search.opds Normal file
View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:odl="http://opds-spec.org/odl"
xml:lang="en"
xmlns="http://www.w3.org/2005/Atom"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:app="http://www.w3.org/2007/app"
xmlns:opds="http://opds-spec.org/2010/catalog"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
<id>{{.S.BaseURL}}/search/?q={{.S.Search}}</id>
<icon>{{.S.BaseURL}}/img/favicon.ico</icon>
<link rel="self"
href="/search/?q={{.S.Search}}&amp;p={{.Page}}&amp;fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link rel="start"
href="/?fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="up"
href="/?fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
{{if .Prev}}
<link rel="first"
href="/search/?q={{.S.Search}}&amp;fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link rel="previous"
href="{{html .Prev}}&amp;fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
{{end}}
{{if .Next}}
<link rel="next"
href="{{html .Next}}&amp;fmt=opds"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
{{end}}
<link rel="search"
title="Search The Imperial Libary of Trantor"
href="/search/?q={searchTerms}&amp;fmt=opds"
type="application/atom+xml"/>
<link rel="search"
href="opensearch.xml"
type="application/opensearchdescription+xml"/>
<opensearch:totalResults>{{.Found}}</opensearch:totalResults>
<opensearch:itemsPerPage>{{.ItemsPage}}</opensearch:itemsPerPage>
<title>search {{.S.Search}}</title>
<author>
<name>The Imperial Library of Trantor</name>
<uri>{{.S.BaseURL}}</uri>
<email>zenow@riseup.net</email>
</author>
<updated>{{.S.Updated}}</updated>
{{$updated := .S.Updated}}
{{$baseurl := .S.BaseURL}}
{{range .Books}}
<entry>
<title>{{html .Title}}</title>
<id>{{$baseurl}}/book/{{.Id}}</id>
<updated>{{$updated}}</updated>
{{range .Author}}
<author>
<name>{{html .}}</name>
</author>
{{end}}
{{if .Contributor}}
<contributor>
<name>{{html .Contributor}}</name>
</contributor>
{{end}}
{{if .Isbn}}
<dcterms:identifier>urn:isbn:{{.Isbn}}</dcterms:identifier>
{{end}}
<dcterms:publisher>{{html .Publisher}}</dcterms:publisher>
{{if .Date}}
<dcterms:issued>{{.Date}}</dcterms:issued>
{{end}}
{{range .Lang}}
<dcterms:language>{{.}}</dcterms:language>
{{end}}
{{range .Subject}}
<category term="{{html .}}"
label="{{html .}}"/>
{{end}}
<summary>{{html .Description}}</summary>
<link type="image/jpeg" href="/cover/{{.Id}}/big/cover.jpg" rel="http://opds-spec.org/image"/>
<link type="image/jpg" href="/cover/{{.Id}}/small/thumbnail.jpg" rel="http://opds-spec.org/image/thumbnail" />
<link rel="http://opds-spec.org/acquisition"
href="/download/{{.Id}}/{{urlquery .Title}}.epub"
type="application/epub+zip" />
</entry>
{{end}}
</feed>

View file

@ -187,6 +187,7 @@ func initRouter(db *database.DB, sg *StatsGatherer) {
r.HandleFunc("/", sg.Gather(indexHandler)) r.HandleFunc("/", sg.Gather(indexHandler))
r.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, ROBOTS_PATH) }) r.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, ROBOTS_PATH) })
r.HandleFunc("/description.json", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, DESCRIPTION_PATH) }) r.HandleFunc("/description.json", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, DESCRIPTION_PATH) })
r.HandleFunc("/opensearch.xml", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, OPENSEARCH_PATH) })
r.HandleFunc("/book/{id:"+id_pattern+"}", sg.Gather(bookHandler)) r.HandleFunc("/book/{id:"+id_pattern+"}", sg.Gather(bookHandler))
r.HandleFunc("/search/", sg.Gather(searchHandler)) r.HandleFunc("/search/", sg.Gather(searchHandler))