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&amp;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 .}}&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>
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}}&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>
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))