Add v0.0.2
This commit is contained in:
parent
d023b869a1
commit
b25ed5c052
|
@ -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
|
||||
|
|
181
OPDS_renderer.php
Normal file
181
OPDS_renderer.php
Normal file
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
10
README
10
README
|
@ -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) {
|
||||
|
|
287
base.php
287
base.php
|
@ -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,41 +76,129 @@ class EntryBook extends Entry
|
|||
$this->book = $pbook;
|
||||
$this->localUpdated = $pbook->timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
class Page
|
||||
{
|
||||
public $title;
|
||||
public $idPage;
|
||||
public $idGet;
|
||||
public $query;
|
||||
public $entryArray = array();
|
||||
|
||||
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 ();
|
||||
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
|
||||
{
|
||||
const PAGE_INDEX = "index";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
?>
|
71
book.php
71
book.php
|
@ -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";
|
||||
?>
|
77
feed.php
77
feed.php
|
@ -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'];
|
||||
$currentPage = Page::getPage ($page, $qid, $query);
|
||||
$currentPage->InitializeContent ();
|
||||
echo $OPDSRender->render ($currentPage);
|
||||
return;
|
||||
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();
|
||||
break;
|
||||
}
|
||||
echo Base::endXmlDocument ();
|
||||
?>
|
||||
|
|
14
fetch.php
14
fetch.php
|
@ -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;
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
Reference in a new issue