@@ -1,2 +1,8 @@ | |||
0.0.2 - 20120411 | |||
* Add support for MOBI and PDF | |||
* Major refactoring to prepare something nice for the future ;) | |||
* Add a config item to make use of X-Sendfile instead of X-Accel-Redirect | |||
if needed | |||
0.0.1 - 20120302 | |||
* First public release |
@@ -0,0 +1,181 @@ | |||
<?php | |||
/** | |||
* COPS (Calibre OPDS PHP Server) class file | |||
* | |||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html) | |||
* @author Sébastien Lucas <sebastien@slucas.fr> | |||
*/ | |||
require_once ("base.php"); | |||
class OPDSRenderer | |||
{ | |||
const PAGE_OPENSEARCH = "8"; | |||
private $xmlStream = NULL; | |||
private $updated = NULL; | |||
private function getUpdatedTime () { | |||
if (is_null ($this->updated)) { | |||
$this->updated = time(); | |||
} | |||
return date (DATE_ATOM, $this->updated); | |||
} | |||
private function getXmlStream () { | |||
if (is_null ($this->xmlStream)) { | |||
$this->xmlStream = new XMLWriter(); | |||
$this->xmlStream->openMemory(); | |||
$this->xmlStream->setIndent (true); | |||
} | |||
return $this->xmlStream; | |||
} | |||
public function getOpenSearch () { | |||
$xml = new XMLWriter (); | |||
$xml->openMemory (); | |||
$xml->setIndent (true); | |||
$xml->startDocument('1.0','UTF-8'); | |||
$xml->startElement ("OpenSearchDescription"); | |||
$xml->startElement ("ShortName"); | |||
$xml->text ("My catalog"); | |||
$xml->endElement (); | |||
$xml->startElement ("InputEncoding"); | |||
$xml->text ("UTF-8"); | |||
$xml->endElement (); | |||
$xml->startElement ("OutputEncoding"); | |||
$xml->text ("UTF-8"); | |||
$xml->endElement (); | |||
$xml->startElement ("Image"); | |||
$xml->text ("favicon.ico"); | |||
$xml->endElement (); | |||
$xml->startElement ("Url"); | |||
$xml->writeAttribute ("type", 'application/atom+xml'); | |||
$xml->writeAttribute ("template", 'feed.php?page=' . self::PAGE_OPENSEARCH_QUERY . '&query={searchTerms}'); | |||
$xml->endElement (); | |||
$xml->endElement (); | |||
$xml->endDocument(); | |||
return $xml->outputMemory(true); | |||
} | |||
private function startXmlDocument ($title) { | |||
self::getXmlStream ()->startDocument('1.0','UTF-8'); | |||
self::getXmlStream ()->startElement ("feed"); | |||
self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/"); | |||
self::getXmlStream ()->startElement ("title"); | |||
self::getXmlStream ()->text ($title); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("id"); | |||
self::getXmlStream ()->text ($_SERVER['REQUEST_URI']); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("updated"); | |||
self::getXmlStream ()->text (self::getUpdatedTime ()); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("icon"); | |||
self::getXmlStream ()->text ("favicon.ico"); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("author"); | |||
self::getXmlStream ()->startElement ("name"); | |||
self::getXmlStream ()->text (utf8_encode ("Sébastien Lucas")); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("uri"); | |||
self::getXmlStream ()->text ("http://blog.slucas.fr"); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("email"); | |||
self::getXmlStream ()->text ("sebastien@slucas.fr"); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->endElement (); | |||
$link = new LinkNavigation ("feed.php", "start", "Home"); | |||
self::renderLink ($link); | |||
$link = new LinkNavigation ($_SERVER['REQUEST_URI'], "self"); | |||
self::renderLink ($link); | |||
$link = new Link ("feed.php?page=" . self::PAGE_OPENSEARCH, "application/opensearchdescription+xml", "search", "Search here"); | |||
self::renderLink ($link); | |||
} | |||
private function endXmlDocument () { | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->endDocument (); | |||
return self::getXmlStream ()->outputMemory(true); | |||
} | |||
private function renderLink ($link) { | |||
self::getXmlStream ()->startElement ("link"); | |||
self::getXmlStream ()->writeAttribute ("href", $link->href); | |||
self::getXmlStream ()->writeAttribute ("type", $link->type); | |||
if (!is_null ($link->rel)) { | |||
self::getXmlStream ()->writeAttribute ("rel", $link->rel); | |||
} | |||
if (!is_null ($link->title)) { | |||
self::getXmlStream ()->writeAttribute ("title", $link->title); | |||
} | |||
self::getXmlStream ()->endElement (); | |||
} | |||
private function renderEntry ($entry) { | |||
self::getXmlStream ()->startElement ("title"); | |||
self::getXmlStream ()->text ($entry->title); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("updated"); | |||
self::getXmlStream ()->text (self::getUpdatedTime ()); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("id"); | |||
self::getXmlStream ()->text ($entry->id); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("content"); | |||
self::getXmlStream ()->writeAttribute ("type", $entry->contentType); | |||
if ($entry->contentType == "text") { | |||
self::getXmlStream ()->text ($entry->content); | |||
} else { | |||
self::getXmlStream ()->writeRaw ($entry->content); | |||
} | |||
self::getXmlStream ()->endElement (); | |||
foreach ($entry->linkArray as $link) { | |||
self::renderLink ($link); | |||
} | |||
if (get_class ($entry) != "EntryBook") { | |||
return; | |||
} | |||
foreach ($entry->book->getAuthors () as $author) { | |||
self::getXmlStream ()->startElement ("author"); | |||
self::getXmlStream ()->startElement ("name"); | |||
self::getXmlStream ()->text ($author->name); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("uri"); | |||
self::getXmlStream ()->text ($author->getUri ()); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->endElement (); | |||
} | |||
foreach ($entry->book->getTags () as $category) { | |||
self::getXmlStream ()->startElement ("category"); | |||
self::getXmlStream ()->writeAttribute ("term", $category); | |||
self::getXmlStream ()->writeAttribute ("label", $category); | |||
self::getXmlStream ()->endElement (); | |||
} | |||
if (!is_null ($entry->book->pubdate)) { | |||
self::getXmlStream ()->startElement ("dcterms:issued"); | |||
self::getXmlStream ()->text (date ("Y-m-d", $entry->book->pubdate)); | |||
self::getXmlStream ()->endElement (); | |||
} | |||
} | |||
public function render ($page) { | |||
self::startXmlDocument ($page->title); | |||
foreach ($page->entryArray as $entry) { | |||
self::getXmlStream ()->startElement ("entry"); | |||
self::renderEntry ($entry); | |||
self::getXmlStream ()->endElement (); | |||
} | |||
return self::endXmlDocument (); | |||
} | |||
} | |||
?> |
@@ -8,7 +8,7 @@ http://opds-validator.appspot.com/ | |||
= Why ? = | |||
In my opinion Calibre is a marvelous tool but is too big and has too much | |||
dependancies to be used for its content server. | |||
dependencies to be used for its content server. | |||
That's the main reason why I coded this OPDS server. I needed a simple | |||
tool to be installed on a small server (Seagate Dockstar in my case). | |||
@@ -17,7 +17,7 @@ I initially thought of Calibre2OPDS but as it generate static file no | |||
search was possible. | |||
So COPS's main advantages are : | |||
* No need for many dependancies. | |||
* No need for many dependencies. | |||
* No need for a lot of CPU or RAM. | |||
* Not much code. | |||
* Search is available. | |||
@@ -77,8 +77,8 @@ application/epub+zip epub; | |||
= Known problems = | |||
* Only tested with Nginx. | |||
* Contain Nginx specific code. | |||
* Only works with EPUB (not MOBI or PDF). | |||
* Contain Nginx specific code (that could be changed with a config item). | |||
* Only works with EPUB, MOBI and PDF. | |||
* certainly many many more. | |||
= Disclaimer = | |||
@@ -88,6 +88,8 @@ with Apache or any other web server. | |||
On the OPDS client side I mainly tested with FBReader and Aldiko on Android. | |||
It also seems to work with Stanza. | |||
= Copyright & License = | |||
COPS - 2012 (c) Sébastien Lucas <sebastien@slucas.fr> | |||
@@ -31,9 +31,10 @@ class Author extends Base { | |||
public static function getCount() { | |||
$nAuthors = parent::getDb ()->query('select count(*) from authors')->fetchColumn(); | |||
parent::addEntryClass ( new Entry ("Authors", self::ALL_AUTHORS_ID, | |||
$entry = new Entry ("Authors", self::ALL_AUTHORS_ID, | |||
"Alphabetical index of the $nAuthors authors", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_AUTHORS)))); | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_AUTHORS))); | |||
return $entry; | |||
} | |||
public static function getAllAuthors() { | |||
@@ -42,13 +43,15 @@ from authors, books_authors_link | |||
where author = authors.id | |||
group by authors.id, authors.name, authors.sort | |||
order by sort'); | |||
$entryArray = array(); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$author = new Author ($post->id, $post->sort); | |||
parent::addEntryClass ( new Entry ($post->sort, $author->getEntryId (), | |||
array_push ($entryArray, new Entry ($post->sort, $author->getEntryId (), | |||
"$post->count books", "text", | |||
array ( new LinkNavigation ($author->getUri ())))); | |||
} | |||
return $entryArray; | |||
} | |||
public static function getAuthorName ($authorId) { | |||
@@ -6,6 +6,12 @@ | |||
* @author Sébastien Lucas <sebastien@slucas.fr> | |||
*/ | |||
function getURLParam ($name, $default = NULL) { | |||
if (!empty ($_GET) && isset($_GET[$name])) { | |||
return $_GET[$name]; | |||
} | |||
return $default; | |||
} | |||
class Link | |||
{ | |||
@@ -20,19 +26,6 @@ class Link | |||
$this->rel = $prel; | |||
$this->title = $ptitle; | |||
} | |||
public function render ($xml) { | |||
$xml->startElement ("link"); | |||
$xml->writeAttribute ("href", $this->href); | |||
$xml->writeAttribute ("type", $this->type); | |||
if (!is_null ($this->rel)) { | |||
$xml->writeAttribute ("rel", $this->rel); | |||
} | |||
if (!is_null ($this->title)) { | |||
$xml->writeAttribute ("title", $this->title); | |||
} | |||
$xml->endElement (); | |||
} | |||
} | |||
class LinkNavigation extends Link | |||
@@ -72,36 +65,6 @@ class Entry | |||
$this->contentType = $pcontentType; | |||
$this->linkArray = $plinkArray; | |||
} | |||
public function renderContent ($xml) { | |||
$xml->startElement ("title"); | |||
$xml->text ($this->title); | |||
$xml->endElement (); | |||
$xml->startElement ("updated"); | |||
$xml->text (self::getUpdatedTime ()); | |||
$xml->endElement (); | |||
$xml->startElement ("id"); | |||
$xml->text ($this->id); | |||
$xml->endElement (); | |||
$xml->startElement ("content"); | |||
$xml->writeAttribute ("type", $this->contentType); | |||
if ($this->contentType == "text") { | |||
$xml->text ($this->content); | |||
} else { | |||
$xml->writeRaw ($this->content); | |||
} | |||
$xml->endElement (); | |||
foreach ($this->linkArray as $link) { | |||
$link->render ($xml); | |||
} | |||
} | |||
public function render ($xml) { | |||
$xml->startElement ("entry"); | |||
self::renderContent ($xml); | |||
$xml->endElement (); | |||
} | |||
} | |||
class EntryBook extends Entry | |||
@@ -113,39 +76,127 @@ class EntryBook extends Entry | |||
$this->book = $pbook; | |||
$this->localUpdated = $pbook->timestamp; | |||
} | |||
public function renderContent ($xml) { | |||
parent::renderContent ($xml); | |||
foreach ($this->book->getAuthors () as $author) { | |||
$xml->startElement ("author"); | |||
$xml->startElement ("name"); | |||
$xml->text ($author->name); | |||
$xml->endElement (); | |||
$xml->startElement ("uri"); | |||
$xml->text ($author->getUri ()); | |||
$xml->endElement (); | |||
$xml->endElement (); | |||
} | |||
foreach ($this->book->getTags () as $category) { | |||
$xml->startElement ("category"); | |||
$xml->writeAttribute ("term", $category); | |||
$xml->writeAttribute ("label", $category); | |||
$xml->endElement (); | |||
} | |||
if (!is_null ($this->book->pubdate)) { | |||
$xml->startElement ("dcterms:issued"); | |||
$xml->text (date ("Y-m-d", $this->book->pubdate)); | |||
$xml->endElement (); | |||
} | |||
class Page | |||
{ | |||
public $title; | |||
public $idPage; | |||
public $idGet; | |||
public $query; | |||
public $entryArray = array(); | |||
public static function getPage ($pageId, $id, $query) | |||
{ | |||
switch ($pageId) { | |||
case Base::PAGE_ALL_AUTHORS : | |||
return new PageAllAuthors ($id, $query); | |||
case Base::PAGE_AUTHOR_DETAIL : | |||
return new PageAuthorDetail ($id, $query); | |||
case Base::PAGE_ALL_SERIES : | |||
return new PageAllSeries ($id, $query); | |||
case Base::PAGE_ALL_BOOKS : | |||
return new PageAllBooks ($id, $query); | |||
case Base::PAGE_ALL_BOOKS_LETTER: | |||
return new PageAllBooksLetter ($id, $query); | |||
case Base::PAGE_ALL_RECENT_BOOKS : | |||
return new PageRecentBooks ($id, $query); | |||
case Base::PAGE_SERIE_DETAIL : | |||
return new PageSerieDetail ($id, $query); | |||
case Base::PAGE_OPENSEARCH_QUERY : | |||
return new PageQueryResult ($id, $query); | |||
break; | |||
default: | |||
return new Page ($id, $query); | |||
} | |||
} | |||
/* Polymorphism is strange with PHP */ | |||
public function render ($xml) { | |||
$xml->startElement ("entry"); | |||
self::renderContent ($xml); | |||
$xml->endElement (); | |||
public function __construct($pid, $pquery) { | |||
$this->idGet = $pid; | |||
$this->query = $pquery; | |||
} | |||
public function InitializeContent () | |||
{ | |||
global $config; | |||
$this->title = $config['cops_title_default']; | |||
array_push ($this->entryArray, Author::getCount()); | |||
array_push ($this->entryArray, Serie::getCount()); | |||
$this->entryArray = array_merge ($this->entryArray, Book::getCount()); | |||
} | |||
} | |||
class PageAllAuthors extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "All authors"; | |||
$this->entryArray = Author::getAllAuthors(); | |||
} | |||
} | |||
class PageAuthorDetail extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = Author::getAuthorName ($this->idGet); | |||
$this->entryArray = Book::getBooksByAuthor ($this->idGet); | |||
} | |||
} | |||
class PageAllSeries extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "All series"; | |||
$this->entryArray = Serie::getAllSeries(); | |||
} | |||
} | |||
class PageSerieDetail extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "Series : " . Serie::getSerieById ($this->idGet)->name; | |||
$this->entryArray = Book::getBooksBySeries ($this->idGet); | |||
} | |||
} | |||
class PageAllBooks extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "All books by starting letter"; | |||
$this->entryArray = Book::getAllBooks (); | |||
} | |||
} | |||
class PageAllBooksLetter extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "All books starting by " . $this->idGet; | |||
$this->entryArray = Book::getBooksByStartingLetter ($this->idGet); | |||
} | |||
} | |||
class PageRecentBooks extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "Most recent books"; | |||
$this->entryArray = Book::getAllRecentBooks (); | |||
} | |||
} | |||
class PageQueryResult extends Page | |||
{ | |||
public function InitializeContent () | |||
{ | |||
$this->title = "Search result for query <" . $this->query . ">"; | |||
$this->entryArray = Book::getBooksByQuery ($this->query); | |||
} | |||
} | |||
abstract class Base | |||
@@ -164,15 +215,6 @@ abstract class Base | |||
const COMPATIBILITY_XML_ALDIKO = "aldiko"; | |||
private static $db = NULL; | |||
private static $xmlStream = NULL; | |||
private static $updated = NULL; | |||
public static function getUpdatedTime () { | |||
if (is_null (self::$updated)) { | |||
self::$updated = time(); | |||
} | |||
return date (DATE_ATOM, self::$updated); | |||
} | |||
public static function getDb () { | |||
global $config; | |||
@@ -185,93 +227,6 @@ abstract class Base | |||
} | |||
} | |||
return self::$db; | |||
} | |||
public static function getXmlStream () { | |||
if (is_null (self::$xmlStream)) { | |||
self::$xmlStream = new XMLWriter(); | |||
self::$xmlStream->openMemory(); | |||
self::$xmlStream->setIndent (true); | |||
} | |||
return self::$xmlStream; | |||
} | |||
public static function getOpenSearch () { | |||
$xml = new XMLWriter (); | |||
$xml->openMemory (); | |||
$xml->setIndent (true); | |||
$xml->startDocument('1.0','UTF-8'); | |||
$xml->startElement ("OpenSearchDescription"); | |||
$xml->startElement ("ShortName"); | |||
$xml->text ("My catalog"); | |||
$xml->endElement (); | |||
$xml->startElement ("InputEncoding"); | |||
$xml->text ("UTF-8"); | |||
$xml->endElement (); | |||
$xml->startElement ("OutputEncoding"); | |||
$xml->text ("UTF-8"); | |||
$xml->endElement (); | |||
$xml->startElement ("Image"); | |||
$xml->text ("favicon.ico"); | |||
$xml->endElement (); | |||
$xml->startElement ("Url"); | |||
$xml->writeAttribute ("type", 'application/atom+xml'); | |||
$xml->writeAttribute ("template", 'feed.php?page=' . self::PAGE_OPENSEARCH_QUERY . '&query={searchTerms}'); | |||
$xml->endElement (); | |||
$xml->endElement (); | |||
$xml->endDocument(); | |||
return $xml->outputMemory(true); | |||
} | |||
public static function startXmlDocument ($title) { | |||
self::getXmlStream ()->startDocument('1.0','UTF-8'); | |||
self::getXmlStream ()->startElement ("feed"); | |||
self::getXmlStream ()->writeAttribute ("xmlns", "http://www.w3.org/2005/Atom"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:xhtml", "http://www.w3.org/1999/xhtml"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:opds", "http://opds-spec.org/2010/catalog"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/"); | |||
self::getXmlStream ()->writeAttribute ("xmlns:dcterms", "http://purl.org/dc/terms/"); | |||
self::getXmlStream ()->startElement ("title"); | |||
self::getXmlStream ()->text ($title); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("id"); | |||
self::getXmlStream ()->text ($_SERVER['REQUEST_URI']); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("updated"); | |||
self::getXmlStream ()->text (self::getUpdatedTime ()); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("icon"); | |||
self::getXmlStream ()->text ("favicon.ico"); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("author"); | |||
self::getXmlStream ()->startElement ("name"); | |||
self::getXmlStream ()->text (utf8_encode ("Sébastien Lucas")); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("uri"); | |||
self::getXmlStream ()->text ("http://blog.slucas.fr"); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->startElement ("email"); | |||
self::getXmlStream ()->text ("sebastien@slucas.fr"); | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->endElement (); | |||
$link = new LinkNavigation ("feed.php", "start", "Home"); | |||
$link->render (self::getXmlStream ()); | |||
$link = new LinkNavigation ($_SERVER['REQUEST_URI'], "self"); | |||
$link->render (self::getXmlStream ()); | |||
$link = new Link ("feed.php?page=" . self::PAGE_OPENSEARCH, "application/opensearchdescription+xml", "search", "Search here"); | |||
$link->render (self::getXmlStream ()); | |||
$link = new LinkNavigation ("feed.php?page=7&id=9", "http://opds-spec.org/shelf", "Biblio"); | |||
$link->render (self::getXmlStream ()); | |||
} | |||
public static function addEntryClass ($entry) { | |||
$entry->render (self::getXmlStream ()); | |||
} | |||
public static function endXmlDocument () { | |||
self::getXmlStream ()->endElement (); | |||
self::getXmlStream ()->endDocument (); | |||
return self::getXmlStream ()->outputMemory(true); | |||
} | |||
} | |||
?> |
@@ -7,6 +7,8 @@ | |||
*/ | |||
require_once('base.php'); | |||
require_once('serie.php'); | |||
require_once('author.php'); | |||
class Book extends Base { | |||
const ALL_BOOKS_ID = "calibre:books"; | |||
@@ -23,6 +25,12 @@ class Book extends Base { | |||
public $authors = NULL; | |||
public $serie = NULL; | |||
public $tags = NULL; | |||
public static $mimetypes = array( | |||
'epub' => 'application/epub+zip', | |||
'mobi' => 'application/x-mobipocket-ebook', | |||
'pdf' => 'application/pdf' | |||
); | |||
public function __construct($pid, $ptitle, $ptimestamp, $ppubdate, $ppath, $pseriesIndex, $pcomment) { | |||
global $config; | |||
@@ -121,14 +129,17 @@ class Book extends Base { | |||
} | |||
array_push ($linkArray, new Link ("fetch.php?id=$this->id&width=50", "image/jpeg", "http://opds-spec.org/image/thumbnail")); | |||
} | |||
if (preg_match ('/epub$/', $file)) { | |||
if (preg_match ('/^\//', $config['calibre_directory'])) | |||
{ | |||
array_push ($linkArray, new Link ("fetch.php?id=$this->id&type=epub", "application/epub+zip", "http://opds-spec.org/acquisition", "Download")); | |||
} | |||
else | |||
{ | |||
array_push ($linkArray, new Link (rawurlencode ($this->path."/".$file), "application/epub+zip", "http://opds-spec.org/acquisition", "Download")); | |||
foreach (self::$mimetypes as $ext => $mime) | |||
{ | |||
if (preg_match ('/'. $ext .'$/', $file)) { | |||
if (preg_match ('/^\//', $config['calibre_directory'])) | |||
{ | |||
array_push ($linkArray, new Link ("fetch.php?id=$this->id&type=" . $ext, $mime, "http://opds-spec.org/acquisition", "Download")); | |||
} | |||
else | |||
{ | |||
array_push ($linkArray, new Link (rawurlencode ($this->path."/".$file), $mime, "http://opds-spec.org/acquisition", "Download")); | |||
} | |||
} | |||
} | |||
} | |||
@@ -148,21 +159,26 @@ class Book extends Base { | |||
public function getEntry () { | |||
parent::addEntryClass (new EntryBook ($this->getTitle (), $this->getEntryId (), | |||
return new EntryBook ($this->getTitle (), $this->getEntryId (), | |||
$this->getComment (), "text/html", | |||
$this->getLinkArray (), $this)); | |||
$this->getLinkArray (), $this); | |||
} | |||
public static function getCount() { | |||
global $config; | |||
$nBooks = parent::getDb ()->query('select count(*) from books')->fetchColumn(); | |||
parent::addEntryClass (new Entry ("Books", | |||
$result = array(); | |||
$entry = new Entry ("Books", | |||
self::ALL_BOOKS_ID, | |||
"Alphabetical index of the $nBooks books", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_BOOKS)))); | |||
parent::addEntryClass (new Entry ("Recents books", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_BOOKS))); | |||
array_push ($result, $entry); | |||
$entry = new Entry ("Recents books", | |||
self::ALL_RECENT_BOOKS_ID, | |||
"Alphabetical index of the 50 most recent books", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_RECENT_BOOKS)))); | |||
"Alphabetical index of the " . $config['cops_recentbooks_limit'] . " most recent books", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_RECENT_BOOKS))); | |||
array_push ($result, $entry); | |||
return $result; | |||
} | |||
public static function getBooksByAuthor($authorId) { | |||
@@ -171,12 +187,14 @@ from books_authors_link, books left outer join comments on comments.book = books | |||
where books_authors_link.book = books.id | |||
and author = ? | |||
order by pubdate'); | |||
$entryArray = array(); | |||
$result->execute (array ($authorId)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
$book->getEntry (); | |||
array_push ($entryArray, $book->getEntry ()); | |||
} | |||
return $entryArray; | |||
} | |||
@@ -185,18 +203,21 @@ order by pubdate'); | |||
from books_series_link, books left outer join comments on comments.book = books.id | |||
where books_series_link.book = books.id and series = ? | |||
order by series_index'); | |||
$entryArray = array(); | |||
$result->execute (array ($serieId)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
$book->getEntry (); | |||
array_push ($entryArray, $book->getEntry ()); | |||
} | |||
return $entryArray; | |||
} | |||
public static function getBookById($bookId) { | |||
$result = parent::getDb ()->prepare('select books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index | |||
from books left outer join comments on book = books.id | |||
where books.id = ?'); | |||
$entryArray = array(); | |||
$result->execute (array ($bookId)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
@@ -211,13 +232,15 @@ where books.id = ?'); | |||
from books left outer join comments on book = books.id | |||
where exists (select null from authors, books_authors_link where book = books.id and author = authors.id and authors.name like ?) | |||
or title like ?"); | |||
$entryArray = array(); | |||
$queryLike = "%" . $query . "%"; | |||
$result->execute (array ($queryLike, $queryLike)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
$book->getEntry (); | |||
array_push ($entryArray, $book->getEntry ()); | |||
} | |||
return $entryArray; | |||
} | |||
public static function getAllBooks() { | |||
@@ -225,25 +248,29 @@ or title like ?"); | |||
from books | |||
group by substr (upper (sort), 1, 1) | |||
order by substr (upper (sort), 1, 1)"); | |||
$entryArray = array(); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
parent::addEntryClass (new Entry ($post->title, "allbooks_" . $post->title, | |||
array_push ($entryArray, new Entry ($post->title, "allbooks_" . $post->title, | |||
"$post->count books", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_BOOKS_LETTER."&id=".$post->title)))); | |||
} | |||
return $entryArray; | |||
} | |||
public static function getBooksByStartingLetter($letter) { | |||
$result = parent::getDb ()->prepare('select books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index | |||
from books left outer join comments on book = books.id | |||
where upper (books.sort) like ?'); | |||
$entryArray = array(); | |||
$queryLike = $letter . "%"; | |||
$result->execute (array ($queryLike)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
$book->getEntry (); | |||
array_push ($entryArray, $book->getEntry ()); | |||
} | |||
return $entryArray; | |||
} | |||
@@ -252,11 +279,13 @@ where upper (books.sort) like ?'); | |||
$result = parent::getDb ()->query("select books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index | |||
from books left outer join comments on book = books.id | |||
order by timestamp desc limit " . $config['cops_recentbooks_limit']); | |||
$entryArray = array(); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
$book->getEntry (); | |||
array_push ($entryArray, $book->getEntry ()); | |||
} | |||
return $entryArray; | |||
} | |||
} |
@@ -18,6 +18,7 @@ | |||
/* | |||
* The internal directory set in nginx config file | |||
* or the same directory as calibre_directory with X-Sendfile | |||
*/ | |||
$config['calibre_internal_directory'] = '/Calibre/'; | |||
@@ -31,4 +32,12 @@ | |||
*/ | |||
$config['cops_title_default'] = "Sebastien's COPS"; | |||
/* | |||
* Wich header to use when downloading books outside the web directory | |||
* Possible values are : | |||
* X-Accel-Redirect : For Nginx | |||
* X-Sendfile : For Lightttpd or Apache (with mod_xsendfile) | |||
*/ | |||
$config['cops_x_accel_redirect'] = "X-Accel-Redirect"; | |||
?> |
@@ -12,75 +12,24 @@ | |||
require_once ("author.php"); | |||
require_once ("serie.php"); | |||
require_once ("book.php"); | |||
require_once ("OPDS_renderer.php"); | |||
header ("Content-Type:application/xml"); | |||
$page = Base::PAGE_INDEX; | |||
global $config; | |||
if (!empty ($_GET) && isset($_GET["page"])) { | |||
$page = $_GET["page"]; | |||
} | |||
$page = getURLParam ("page", Base::PAGE_INDEX); | |||
$query = getURLParam ("query"); | |||
$qid = getURLParam ("id"); | |||
$OPDSRender = new OPDSRenderer (); | |||
switch ($page) { | |||
case Base::PAGE_ALL_AUTHORS : | |||
$title = "All authors"; | |||
break; | |||
case Base::PAGE_AUTHOR_DETAIL : | |||
$title = Author::getAuthorName ($_GET["id"]); | |||
break; | |||
case Base::PAGE_ALL_SERIES : | |||
$title = "All series"; | |||
break; | |||
case Base::PAGE_ALL_BOOKS : | |||
$title = "All books by starting letter"; | |||
break; | |||
case Base::PAGE_ALL_BOOKS_LETTER: | |||
$title = "All books starting by " . $_GET["id"]; | |||
break; | |||
case Base::PAGE_ALL_RECENT_BOOKS : | |||
$title = "Most recent books"; | |||
break; | |||
case Base::PAGE_SERIE_DETAIL : | |||
$title = "Series : " . Serie::getSerieById ($_GET["id"])->name; | |||
break; | |||
case Base::PAGE_OPENSEARCH : | |||
echo Base::getOpenSearch (); | |||
echo $OPDSRender->getOpenSearch (); | |||
return; | |||
case Base::PAGE_OPENSEARCH_QUERY : | |||
$title = "Search result for query <" . $_GET["query"] . ">"; | |||
break; | |||
default: | |||
$title = $config['cops_title_default']; | |||
break; | |||
} | |||
Base::startXmlDocument ($title); | |||
switch ($page) { | |||
case Base::PAGE_ALL_AUTHORS : | |||
Author::getAllAuthors(); | |||
break; | |||
case Base::PAGE_AUTHOR_DETAIL : | |||
Book::getBooksByAuthor ($_GET["id"]); | |||
break; | |||
case Base::PAGE_ALL_SERIES : | |||
Serie::getAllSeries(); | |||
break; | |||
case Base::PAGE_ALL_BOOKS : | |||
Book::getAllBooks (); | |||
break; | |||
case Base::PAGE_ALL_BOOKS_LETTER: | |||
Book::getBooksByStartingLetter ($_GET["id"]); | |||
break; | |||
case Base::PAGE_ALL_RECENT_BOOKS : | |||
Book::getAllRecentBooks (); | |||
break; | |||
case Base::PAGE_SERIE_DETAIL : | |||
Book::getBooksBySeries ($_GET["id"]); | |||
break; | |||
case Base::PAGE_OPENSEARCH_QUERY : | |||
Book::getBooksByQuery ($_GET["query"]); | |||
break; | |||
default: | |||
Author::getCount(); | |||
Serie::getCount(); | |||
Book::getCount(); | |||
$currentPage = Page::getPage ($page, $qid, $query); | |||
$currentPage->InitializeContent (); | |||
echo $OPDSRender->render ($currentPage); | |||
return; | |||
break; | |||
} | |||
echo Base::endXmlDocument (); | |||
?> |
@@ -7,17 +7,13 @@ | |||
*/ | |||
require_once ("config.php"); | |||
require_once('book.php'); | |||
require_once ("book.php"); | |||
global $config; | |||
$bookId = $_GET["id"]; | |||
$book = Book::getBookById($bookId); | |||
$type = "jpg"; | |||
$type = getURLParam ("type", "jpg"); | |||
if (!empty ($_GET) && isset($_GET["type"])) { | |||
$type = $_GET["type"]; | |||
} | |||
switch ($type) | |||
{ | |||
case "jpg": | |||
@@ -48,11 +44,11 @@ | |||
return; | |||
} | |||
break; | |||
case "epub": | |||
header("Content-type: application/epub+zip"); | |||
default: | |||
header("Content-type: " . Book::$mimetypes[$type]); | |||
break; | |||
} | |||
$file = $book->getFilePath ($type, true); | |||
header('Content-Disposition: attachement; filename="' . basename ($file) . '"'); | |||
header ("X-Accel-Redirect: " . $config['calibre_internal_directory'] . $file); | |||
header ($config['cops_x_accel_redirect'] . ": " . $config['calibre_internal_directory'] . $file); | |||
?> |
@@ -25,9 +25,10 @@ class Serie extends Base { | |||
public static function getCount() { | |||
$nSeries = parent::getDb ()->query('select count(*) from series')->fetchColumn(); | |||
parent::addEntryClass (new Entry ("Series", self::ALL_SERIES_ID, | |||
$entry = new Entry ("Series", self::ALL_SERIES_ID, | |||
"Alphabetical index of the $nSeries series", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_SERIES)))); | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_SERIES))); | |||
return $entry; | |||
} | |||
public static function getSerieByBookId ($bookId) { | |||
@@ -56,12 +57,14 @@ from series, books_series_link | |||
where series.id = series | |||
group by series.id, series.name, series.sort | |||
order by series.sort'); | |||
$entryArray = array(); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
parent::addEntryClass (new Entry ($post->sort, self::ALL_SERIES_ID.":".$post->id, | |||
array_push ($entryArray, new Entry ($post->sort, self::ALL_SERIES_ID.":".$post->id, | |||
"$post->count books", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_SERIE_DETAIL."&id=$post->id")))); | |||
} | |||
return $entryArray; | |||
} | |||
} | |||
?> |