Add v0.0.3

This commit is contained in:
Sébastien Lucas 2012-05-28 07:06:12 +02:00
parent f460bd8731
commit 2308af6033
10 changed files with 146 additions and 24 deletions

View file

@ -1,8 +1,17 @@
0.0.3 - 20120507
* Fixed many things blocking opensearch from working
* There was a bug introduced in 0.0.2
* The URL can't be relative for Mantano reader, so I added a configuration item.
* I continued the refactoring to bring HTML to COPS
* Thumbnails have bigger size (I'll add a configuration item later)
* Add headers to help caching image and thumbnail to the browser
*
0.0.2 - 20120411 0.0.2 - 20120411
* Add support for MOBI and PDF * Add support for MOBI and PDF
* Major refactoring to prepare something nice for the future ;) * Major refactoring to prepare something nice for the future ;)
* Add a config item to make use of X-Sendfile instead of X-Accel-Redirect * Add a config item to make use of X-Sendfile instead of X-Accel-Redirect if needed
if needed
0.0.1 - 20120302 0.0.1 - 20120302
* First public release * First public release

View file

@ -11,6 +11,7 @@ require_once ("base.php");
class OPDSRenderer class OPDSRenderer
{ {
const PAGE_OPENSEARCH = "8"; const PAGE_OPENSEARCH = "8";
const PAGE_OPENSEARCH_QUERY = "9";
private $xmlStream = NULL; private $xmlStream = NULL;
private $updated = NULL; private $updated = NULL;
@ -32,11 +33,13 @@ class OPDSRenderer
} }
public function getOpenSearch () { public function getOpenSearch () {
global $config;
$xml = new XMLWriter (); $xml = new XMLWriter ();
$xml->openMemory (); $xml->openMemory ();
$xml->setIndent (true); $xml->setIndent (true);
$xml->startDocument('1.0','UTF-8'); $xml->startDocument('1.0','UTF-8');
$xml->startElement ("OpenSearchDescription"); $xml->startElement ("OpenSearchDescription");
$xml->writeAttribute ("xmlns", "http://a9.com/-/spec/opensearch/1.1/");
$xml->startElement ("ShortName"); $xml->startElement ("ShortName");
$xml->text ("My catalog"); $xml->text ("My catalog");
$xml->endElement (); $xml->endElement ();
@ -47,11 +50,18 @@ class OPDSRenderer
$xml->text ("UTF-8"); $xml->text ("UTF-8");
$xml->endElement (); $xml->endElement ();
$xml->startElement ("Image"); $xml->startElement ("Image");
$xml->writeAttribute ("type", "image/x-icon");
$xml->writeAttribute ("width", "16");
$xml->writeAttribute ("height", "16");
$xml->text ("favicon.ico"); $xml->text ("favicon.ico");
$xml->endElement (); $xml->endElement ();
$xml->startElement ("Url"); $xml->startElement ("Url");
$xml->writeAttribute ("type", 'application/atom+xml'); $xml->writeAttribute ("type", 'application/atom+xml');
$xml->writeAttribute ("template", 'feed.php?page=' . self::PAGE_OPENSEARCH_QUERY . '&query={searchTerms}'); $xml->writeAttribute ("template", $config['cops_full_url'] . 'feed.php?query={searchTerms}');
$xml->endElement ();
$xml->startElement ("Query");
$xml->writeAttribute ("role", "example");
$xml->writeAttribute ("searchTerms", "robot");
$xml->endElement (); $xml->endElement ();
$xml->endElement (); $xml->endElement ();
$xml->endDocument(); $xml->endDocument();
@ -89,9 +99,9 @@ class OPDSRenderer
self::getXmlStream ()->text ("sebastien@slucas.fr"); self::getXmlStream ()->text ("sebastien@slucas.fr");
self::getXmlStream ()->endElement (); self::getXmlStream ()->endElement ();
self::getXmlStream ()->endElement (); self::getXmlStream ()->endElement ();
$link = new LinkNavigation ("feed.php", "start", "Home"); $link = new LinkNavigation ("", "start", "Home");
self::renderLink ($link); self::renderLink ($link);
$link = new LinkNavigation ($_SERVER['REQUEST_URI'], "self"); $link = new LinkNavigation ("?" . $_SERVER['QUERY_STRING'], "self");
self::renderLink ($link); self::renderLink ($link);
$link = new Link ("feed.php?page=" . self::PAGE_OPENSEARCH, "application/opensearchdescription+xml", "search", "Search here"); $link = new Link ("feed.php?page=" . self::PAGE_OPENSEARCH, "application/opensearchdescription+xml", "search", "Search here");
self::renderLink ($link); self::renderLink ($link);
@ -169,6 +179,18 @@ class OPDSRenderer
public function render ($page) { public function render ($page) {
self::startXmlDocument ($page->title); self::startXmlDocument ($page->title);
if ($page->query)
{
self::getXmlStream ()->startElement ("opensearch:totalResults");
self::getXmlStream ()->text (count($page->entryArray));
self::getXmlStream ()->endElement ();
self::getXmlStream ()->startElement ("opensearch:itemsPerPage");
self::getXmlStream ()->text (count($page->entryArray));
self::getXmlStream ()->endElement ();
self::getXmlStream ()->startElement ("opensearch:startIndex");
self::getXmlStream ()->text ("1");
self::getXmlStream ()->endElement ();
}
foreach ($page->entryArray as $entry) { foreach ($page->entryArray as $entry) {
self::getXmlStream ()->startElement ("entry"); self::getXmlStream ()->startElement ("entry");
self::renderEntry ($entry); self::renderEntry ($entry);
@ -178,4 +200,4 @@ class OPDSRenderer
} }
} }
?> ?>

8
README
View file

@ -74,6 +74,14 @@ If you choose to put your Calibre directory inside your web directory then you
will have to edit /etc/nginx/mime.types to add this line : will have to edit /etc/nginx/mime.types to add this line :
application/epub+zip epub; application/epub+zip epub;
= Notes on Opensearch =
Opensearch allow searching through an OPDS catalog. After many tests, I've been
able to make it work with FBReader and Mantano Reader.
It seems that Aldiko didn't implement it properly so it won't work with COPS or
any other custom OPDS catalog.
= Known problems = = Known problems =
* Only tested with Nginx. * Only tested with Nginx.

View file

@ -21,7 +21,7 @@ class Author extends Base {
} }
public function getUri () { public function getUri () {
return "feed.php?page=".parent::PAGE_AUTHOR_DETAIL."&id=$this->id"; return "?page=".parent::PAGE_AUTHOR_DETAIL."&id=$this->id";
} }
public function getEntryId () { public function getEntryId () {
@ -33,7 +33,7 @@ class Author extends Base {
$nAuthors = parent::getDb ()->query('select count(*) from authors')->fetchColumn(); $nAuthors = parent::getDb ()->query('select count(*) from authors')->fetchColumn();
$entry = new Entry ("Authors", self::ALL_AUTHORS_ID, $entry = new Entry ("Authors", self::ALL_AUTHORS_ID,
"Alphabetical index of the $nAuthors authors", "text", "Alphabetical index of the $nAuthors authors", "text",
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_AUTHORS))); array ( new LinkNavigation ("?page=".parent::PAGE_ALL_AUTHORS)));
return $entry; return $entry;
} }

View file

@ -26,6 +26,10 @@ class Link
$this->rel = $prel; $this->rel = $prel;
$this->title = $ptitle; $this->title = $ptitle;
} }
public function hrefXhtml () {
return str_replace ("&", "&", $this->href);
}
} }
class LinkNavigation extends Link class LinkNavigation extends Link
@ -34,6 +38,7 @@ class LinkNavigation extends Link
public function __construct($phref, $prel = NULL, $ptitle = NULL) { public function __construct($phref, $prel = NULL, $ptitle = NULL) {
parent::__construct ($phref, self::OPDS_NAVIGATION_TYPE, $prel, $ptitle); parent::__construct ($phref, self::OPDS_NAVIGATION_TYPE, $prel, $ptitle);
$this->href = $_SERVER["SCRIPT_NAME"] . $this->href;
} }
} }
@ -76,6 +81,14 @@ class EntryBook extends Entry
$this->book = $pbook; $this->book = $pbook;
$this->localUpdated = $pbook->timestamp; $this->localUpdated = $pbook->timestamp;
} }
public function getCoverThumbnail () {
foreach ($this->linkArray as $link) {
if ($link->rel == "http://opds-spec.org/image/thumbnail")
return $link->hrefXhtml ();
}
return null;
}
} }
class Page class Page

View file

@ -25,6 +25,7 @@ class Book extends Base {
public $authors = NULL; public $authors = NULL;
public $serie = NULL; public $serie = NULL;
public $tags = NULL; public $tags = NULL;
public $format = array ();
public static $mimetypes = array( public static $mimetypes = array(
'epub' => 'application/epub+zip', 'epub' => 'application/epub+zip',
'mobi' => 'application/x-mobipocket-ebook', 'mobi' => 'application/x-mobipocket-ebook',
@ -59,6 +60,20 @@ class Book extends Base {
return $this->authors; return $this->authors;
} }
public function getAuthorsName () {
$authorList = null;
foreach ($this->getAuthors () as $author) {
if ($authorList) {
$authorList = $authorList . ", " . $author->name;
}
else
{
$authorList = $author->name;
}
}
return $authorList;
}
public function getSerie () { public function getSerie () {
if (is_null ($this->serie)) { if (is_null ($this->serie)) {
$this->serie = Serie::getSerieByBookId ($this->id); $this->serie = Serie::getSerieByBookId ($this->id);
@ -84,6 +99,20 @@ class Book extends Base {
return $this->tags; return $this->tags;
} }
public function getTagsName () {
$tagList = null;
foreach ($this->getTags () as $tag) {
if ($tagList) {
$tagList = $tagList . ", " . $tag;
}
else
{
$tagList = $tag;
}
}
return $tagList;
}
public function getComment () { public function getComment () {
$addition = ""; $addition = "";
$se = $this->getSerie (); $se = $this->getSerie ();
@ -125,20 +154,21 @@ class Book extends Base {
} }
else else
{ {
array_push ($linkArray, new Link (rawurlencode ($this->path."/".$file), "image/jpeg", "http://opds-spec.org/image")); array_push ($linkArray, new Link (str_replace('%2F','/',rawurlencode ($this->path."/".$file)), "image/jpeg", "http://opds-spec.org/image"));
} }
array_push ($linkArray, new Link ("fetch.php?id=$this->id&width=50", "image/jpeg", "http://opds-spec.org/image/thumbnail")); array_push ($linkArray, new Link ("fetch.php?id=$this->id&height=70", "image/jpeg", "http://opds-spec.org/image/thumbnail"));
} }
foreach (self::$mimetypes as $ext => $mime) foreach (self::$mimetypes as $ext => $mime)
{ {
if (preg_match ('/'. $ext .'$/', $file)) { if (preg_match ('/'. $ext .'$/', $file)) {
$this->format [$ext] = $file;
if (preg_match ('/^\//', $config['calibre_directory'])) 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")); array_push ($linkArray, new Link ("fetch.php?id=$this->id&type=" . $ext, $mime, "http://opds-spec.org/acquisition", "Download"));
} }
else else
{ {
array_push ($linkArray, new Link (rawurlencode ($this->path."/".$file), $mime, "http://opds-spec.org/acquisition", "Download")); array_push ($linkArray, new Link (str_replace('%2F','/',rawurlencode ($this->path."/".$file)), $mime, "http://opds-spec.org/acquisition", "Download"));
} }
} }
} }
@ -171,12 +201,12 @@ class Book extends Base {
$entry = new Entry ("Books", $entry = new Entry ("Books",
self::ALL_BOOKS_ID, self::ALL_BOOKS_ID,
"Alphabetical index of the $nBooks books", "text", "Alphabetical index of the $nBooks books", "text",
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_BOOKS))); array ( new LinkNavigation ("?page=".parent::PAGE_ALL_BOOKS)));
array_push ($result, $entry); array_push ($result, $entry);
$entry = new Entry ("Recents books", $entry = new Entry ("Recents books",
self::ALL_RECENT_BOOKS_ID, self::ALL_RECENT_BOOKS_ID,
"Alphabetical index of the " . $config['cops_recentbooks_limit'] . " most recent books", "text", "Alphabetical index of the " . $config['cops_recentbooks_limit'] . " most recent books", "text",
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_RECENT_BOOKS))); array ( new LinkNavigation ("?page=".parent::PAGE_ALL_RECENT_BOOKS)));
array_push ($result, $entry); array_push ($result, $entry);
return $result; return $result;
} }
@ -253,7 +283,7 @@ order by substr (upper (sort), 1, 1)");
{ {
array_push ($entryArray, new Entry ($post->title, "allbooks_" . $post->title, array_push ($entryArray, new Entry ($post->title, "allbooks_" . $post->title,
"$post->count books", "text", "$post->count books", "text",
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_BOOKS_LETTER."&id=".$post->title)))); array ( new LinkNavigation ("?page=".parent::PAGE_ALL_BOOKS_LETTER."&id=".$post->title))));
} }
return $entryArray; return $entryArray;
} }

View file

@ -6,13 +6,15 @@
* @author Sébastien Lucas <sebastien@slucas.fr> * @author Sébastien Lucas <sebastien@slucas.fr>
*/ */
$config = array(); if (!isset($config))
$config = array();
/* /*
* The directory containing calibre's metadata.db file, with sub-directories * The directory containing calibre's metadata.db file, with sub-directories
* containing all the formats. * containing all the formats.
* If this directory starts with a / EPUB download will only work with Nginx * If this directory starts with a / EPUB download will only work with Nginx
* and if the calibre_internal_directory is set * and the calibre_internal_directory has to be set properly
* BEWARE : it has to end with a /
*/ */
$config['calibre_directory'] = './'; $config['calibre_directory'] = './';
@ -23,7 +25,14 @@
$config['calibre_internal_directory'] = '/Calibre/'; $config['calibre_internal_directory'] = '/Calibre/';
/* /*
* Number of books * Full URL prefix (with trailing /)
* usefull especially for Opensearch where a full URL is sometimes required
* For example Mantano requires it.
*/
$config['cops_full_url'] = '';
/*
* Number of recent books to show
*/ */
$config['cops_recentbooks_limit'] = '50'; $config['cops_recentbooks_limit'] = '50';
@ -39,5 +48,5 @@
* X-Accel-Redirect : For Nginx * X-Accel-Redirect : For Nginx
* X-Sendfile : For Lightttpd or Apache (with mod_xsendfile) * X-Sendfile : For Lightttpd or Apache (with mod_xsendfile)
*/ */
$config['cops_x_accel_redirect'] = "X-Accel-Redirect"; $config['cops_x_accel_redirect'] = "X-Accel-Redirect";
?> ?>

View file

@ -17,6 +17,8 @@
header ("Content-Type:application/xml"); header ("Content-Type:application/xml");
$page = getURLParam ("page", Base::PAGE_INDEX); $page = getURLParam ("page", Base::PAGE_INDEX);
$query = getURLParam ("query"); $query = getURLParam ("query");
if ($query)
$page = Base::PAGE_OPENSEARCH_QUERY;
$qid = getURLParam ("id"); $qid = getURLParam ("id");
$OPDSRender = new OPDSRenderer (); $OPDSRender = new OPDSRenderer ();

View file

@ -10,6 +10,10 @@
require_once ("book.php"); require_once ("book.php");
global $config; global $config;
$expires = 60*60*24*14;
header("Pragma: public");
header("Cache-Control: maxage=".$expires);
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
$bookId = $_GET["id"]; $bookId = $_GET["id"];
$book = Book::getBookById($bookId); $book = Book::getBookById($bookId);
$type = getURLParam ("type", "jpg"); $type = getURLParam ("type", "jpg");
@ -43,6 +47,31 @@
imagedestroy($dst_img); imagedestroy($dst_img);
return; return;
} }
if (isset($_GET["height"]))
{
$file = $book->getFilePath ($type);
// get image size
if($size = GetImageSize($file)){
$w = $size[0];
$h = $size[1];
//set new size
$nh = $_GET["height"];
$nw = ($nh*$w)/$h;
}
else{
//set new size
$nw = "160";
$nh = "120";
}
//draw the image
$src_img = imagecreatefromjpeg($file);
$dst_img = imagecreatetruecolor($nw,$nh);
imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $nw, $nh, $w, $h);//resizing the image
imagejpeg($dst_img,"",100);
imagedestroy($src_img);
imagedestroy($dst_img);
return;
}
break; break;
default: default:
header("Content-type: " . Book::$mimetypes[$type]); header("Content-type: " . Book::$mimetypes[$type]);

View file

@ -20,14 +20,14 @@ class Serie extends Base {
} }
public function getUri () { public function getUri () {
return "feed.php?page=".parent::PAGE_SERIE_DETAIL."&id=$this->id"; return "?page=".parent::PAGE_SERIE_DETAIL."&id=$this->id";
} }
public static function getCount() { public static function getCount() {
$nSeries = parent::getDb ()->query('select count(*) from series')->fetchColumn(); $nSeries = parent::getDb ()->query('select count(*) from series')->fetchColumn();
$entry = new Entry ("Series", self::ALL_SERIES_ID, $entry = new Entry ("Series", self::ALL_SERIES_ID,
"Alphabetical index of the $nSeries series", "text", "Alphabetical index of the $nSeries series", "text",
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_SERIES))); array ( new LinkNavigation ("?page=".parent::PAGE_ALL_SERIES)));
return $entry; return $entry;
} }
@ -62,7 +62,7 @@ order by series.sort');
{ {
array_push ($entryArray, 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", "$post->count books", "text",
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_SERIE_DETAIL."&id=$post->id")))); array ( new LinkNavigation ("?page=".parent::PAGE_SERIE_DETAIL."&id=$post->id"))));
} }
return $entryArray; return $entryArray;
} }