From b0a0b68561af4364434ec65fba1ae96190c06fcd Mon Sep 17 00:00:00 2001 From: Las Zenow <zenow@riseup.net> Date: Sat, 17 Jan 2015 02:19:14 -0600 Subject: [PATCH] Basic OPDS support --- config.go | 1 + opensearch.xml | 11 +++++ template.go | 10 +++++ templates/index.opds | 52 ++++++++++++++++++++++ templates/search.opds | 100 ++++++++++++++++++++++++++++++++++++++++++ trantor.go | 1 + 6 files changed, 175 insertions(+) create mode 100644 opensearch.xml create mode 100644 templates/index.opds create mode 100644 templates/search.opds diff --git a/config.go b/config.go index 14be217..2fd74f7 100644 --- a/config.go +++ b/config.go @@ -34,6 +34,7 @@ const ( IMG_PATH = "img/" ROBOTS_PATH = "robots.txt" DESCRIPTION_PATH = "description.json" + OPENSEARCH_PATH = "opensearch.xml" LOGGER_CONFIG = "logger.xml" IMG_WIDTH_BIG = 300 diff --git a/opensearch.xml b/opensearch.xml new file mode 100644 index 0000000..6f6dd60 --- /dev/null +++ b/opensearch.xml @@ -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&q={searchTerms}"/> + </OpenSearchDescription> diff --git a/template.go b/template.go index 4e9225c..69a600f 100644 --- a/template.go +++ b/template.go @@ -9,6 +9,7 @@ import ( "errors" "html/template" "net/http" + "time" "git.gitorious.org/trantor/trantor.git/database" ) @@ -20,6 +21,7 @@ type Status struct { User string IsAdmin bool Notif []Notification + Updated string Home bool About bool News bool @@ -36,6 +38,7 @@ func GetStatus(h handler) Status { s.User = h.sess.User s.IsAdmin = h.sess.IsAdmin() s.Notif = h.sess.GetNotif() + s.Updated = time.Now().UTC().Format("2006-01-02T15:04:05Z") h.sess.Save(h.w, h.r) return s } @@ -66,12 +69,19 @@ var tmpl_rss = txt_tmpl.Must(txt_tmpl.ParseFiles( 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{}) { var err error fmt := h.r.FormValue("fmt") switch fmt { case "rss": err = tmpl_rss.ExecuteTemplate(h.w, tmpl+".rss", data) + case "opds": + err = tmpl_opds.ExecuteTemplate(h.w, tmpl+".opds", data) case "json": err = loadJson(h.w, tmpl, data) default: diff --git a/templates/index.opds b/templates/index.opds new file mode 100644 index 0000000..85c57cb --- /dev/null +++ b/templates/index.opds @@ -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 .}}&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> diff --git a/templates/search.opds b/templates/search.opds new file mode 100644 index 0000000..4c00ef6 --- /dev/null +++ b/templates/search.opds @@ -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}}&p={{.Page}}&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}}&fmt=opds" + type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> + <link rel="previous" + href="{{html .Prev}}&fmt=opds" + type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> + {{end}} + {{if .Next}} + <link rel="next" + href="{{html .Next}}&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}&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> diff --git a/trantor.go b/trantor.go index ed16ab7..36b9fce 100644 --- a/trantor.go +++ b/trantor.go @@ -187,6 +187,7 @@ func initRouter(db *database.DB, sg *StatsGatherer) { r.HandleFunc("/", sg.Gather(indexHandler)) 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("/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("/search/", sg.Gather(searchHandler))