@@ -0,0 +1,2 @@ | |||
0.0.1 - 20120302 | |||
* First public release |
@@ -0,0 +1,340 @@ | |||
GNU GENERAL PUBLIC LICENSE | |||
Version 2, June 1991 | |||
Copyright (C) 1989, 1991 Free Software Foundation, Inc. | |||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||
Everyone is permitted to copy and distribute verbatim copies | |||
of this license document, but changing it is not allowed. | |||
Preamble | |||
The licenses for most software are designed to take away your | |||
freedom to share and change it. By contrast, the GNU General Public | |||
License is intended to guarantee your freedom to share and change free | |||
software--to make sure the software is free for all its users. This | |||
General Public License applies to most of the Free Software | |||
Foundation's software and to any other program whose authors commit to | |||
using it. (Some other Free Software Foundation software is covered by | |||
the GNU Library General Public License instead.) You can apply it to | |||
your programs, too. | |||
When we speak of free software, we are referring to freedom, not | |||
price. Our General Public Licenses are designed to make sure that you | |||
have the freedom to distribute copies of free software (and charge for | |||
this service if you wish), that you receive source code or can get it | |||
if you want it, that you can change the software or use pieces of it | |||
in new free programs; and that you know you can do these things. | |||
To protect your rights, we need to make restrictions that forbid | |||
anyone to deny you these rights or to ask you to surrender the rights. | |||
These restrictions translate to certain responsibilities for you if you | |||
distribute copies of the software, or if you modify it. | |||
For example, if you distribute copies of such a program, whether | |||
gratis or for a fee, you must give the recipients all the rights that | |||
you have. You must make sure that they, too, receive or can get the | |||
source code. And you must show them these terms so they know their | |||
rights. | |||
We protect your rights with two steps: (1) copyright the software, and | |||
(2) offer you this license which gives you legal permission to copy, | |||
distribute and/or modify the software. | |||
Also, for each author's protection and ours, we want to make certain | |||
that everyone understands that there is no warranty for this free | |||
software. If the software is modified by someone else and passed on, we | |||
want its recipients to know that what they have is not the original, so | |||
that any problems introduced by others will not reflect on the original | |||
authors' reputations. | |||
Finally, any free program is threatened constantly by software | |||
patents. We wish to avoid the danger that redistributors of a free | |||
program will individually obtain patent licenses, in effect making the | |||
program proprietary. To prevent this, we have made it clear that any | |||
patent must be licensed for everyone's free use or not licensed at all. | |||
The precise terms and conditions for copying, distribution and | |||
modification follow. | |||
GNU GENERAL PUBLIC LICENSE | |||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |||
0. This License applies to any program or other work which contains | |||
a notice placed by the copyright holder saying it may be distributed | |||
under the terms of this General Public License. The "Program", below, | |||
refers to any such program or work, and a "work based on the Program" | |||
means either the Program or any derivative work under copyright law: | |||
that is to say, a work containing the Program or a portion of it, | |||
either verbatim or with modifications and/or translated into another | |||
language. (Hereinafter, translation is included without limitation in | |||
the term "modification".) Each licensee is addressed as "you". | |||
Activities other than copying, distribution and modification are not | |||
covered by this License; they are outside its scope. The act of | |||
running the Program is not restricted, and the output from the Program | |||
is covered only if its contents constitute a work based on the | |||
Program (independent of having been made by running the Program). | |||
Whether that is true depends on what the Program does. | |||
1. You may copy and distribute verbatim copies of the Program's | |||
source code as you receive it, in any medium, provided that you | |||
conspicuously and appropriately publish on each copy an appropriate | |||
copyright notice and disclaimer of warranty; keep intact all the | |||
notices that refer to this License and to the absence of any warranty; | |||
and give any other recipients of the Program a copy of this License | |||
along with the Program. | |||
You may charge a fee for the physical act of transferring a copy, and | |||
you may at your option offer warranty protection in exchange for a fee. | |||
2. You may modify your copy or copies of the Program or any portion | |||
of it, thus forming a work based on the Program, and copy and | |||
distribute such modifications or work under the terms of Section 1 | |||
above, provided that you also meet all of these conditions: | |||
a) You must cause the modified files to carry prominent notices | |||
stating that you changed the files and the date of any change. | |||
b) You must cause any work that you distribute or publish, that in | |||
whole or in part contains or is derived from the Program or any | |||
part thereof, to be licensed as a whole at no charge to all third | |||
parties under the terms of this License. | |||
c) If the modified program normally reads commands interactively | |||
when run, you must cause it, when started running for such | |||
interactive use in the most ordinary way, to print or display an | |||
announcement including an appropriate copyright notice and a | |||
notice that there is no warranty (or else, saying that you provide | |||
a warranty) and that users may redistribute the program under | |||
these conditions, and telling the user how to view a copy of this | |||
License. (Exception: if the Program itself is interactive but | |||
does not normally print such an announcement, your work based on | |||
the Program is not required to print an announcement.) | |||
These requirements apply to the modified work as a whole. If | |||
identifiable sections of that work are not derived from the Program, | |||
and can be reasonably considered independent and separate works in | |||
themselves, then this License, and its terms, do not apply to those | |||
sections when you distribute them as separate works. But when you | |||
distribute the same sections as part of a whole which is a work based | |||
on the Program, the distribution of the whole must be on the terms of | |||
this License, whose permissions for other licensees extend to the | |||
entire whole, and thus to each and every part regardless of who wrote it. | |||
Thus, it is not the intent of this section to claim rights or contest | |||
your rights to work written entirely by you; rather, the intent is to | |||
exercise the right to control the distribution of derivative or | |||
collective works based on the Program. | |||
In addition, mere aggregation of another work not based on the Program | |||
with the Program (or with a work based on the Program) on a volume of | |||
a storage or distribution medium does not bring the other work under | |||
the scope of this License. | |||
3. You may copy and distribute the Program (or a work based on it, | |||
under Section 2) in object code or executable form under the terms of | |||
Sections 1 and 2 above provided that you also do one of the following: | |||
a) Accompany it with the complete corresponding machine-readable | |||
source code, which must be distributed under the terms of Sections | |||
1 and 2 above on a medium customarily used for software interchange; or, | |||
b) Accompany it with a written offer, valid for at least three | |||
years, to give any third party, for a charge no more than your | |||
cost of physically performing source distribution, a complete | |||
machine-readable copy of the corresponding source code, to be | |||
distributed under the terms of Sections 1 and 2 above on a medium | |||
customarily used for software interchange; or, | |||
c) Accompany it with the information you received as to the offer | |||
to distribute corresponding source code. (This alternative is | |||
allowed only for noncommercial distribution and only if you | |||
received the program in object code or executable form with such | |||
an offer, in accord with Subsection b above.) | |||
The source code for a work means the preferred form of the work for | |||
making modifications to it. For an executable work, complete source | |||
code means all the source code for all modules it contains, plus any | |||
associated interface definition files, plus the scripts used to | |||
control compilation and installation of the executable. However, as a | |||
special exception, the source code distributed need not include | |||
anything that is normally distributed (in either source or binary | |||
form) with the major components (compiler, kernel, and so on) of the | |||
operating system on which the executable runs, unless that component | |||
itself accompanies the executable. | |||
If distribution of executable or object code is made by offering | |||
access to copy from a designated place, then offering equivalent | |||
access to copy the source code from the same place counts as | |||
distribution of the source code, even though third parties are not | |||
compelled to copy the source along with the object code. | |||
4. You may not copy, modify, sublicense, or distribute the Program | |||
except as expressly provided under this License. Any attempt | |||
otherwise to copy, modify, sublicense or distribute the Program is | |||
void, and will automatically terminate your rights under this License. | |||
However, parties who have received copies, or rights, from you under | |||
this License will not have their licenses terminated so long as such | |||
parties remain in full compliance. | |||
5. You are not required to accept this License, since you have not | |||
signed it. However, nothing else grants you permission to modify or | |||
distribute the Program or its derivative works. These actions are | |||
prohibited by law if you do not accept this License. Therefore, by | |||
modifying or distributing the Program (or any work based on the | |||
Program), you indicate your acceptance of this License to do so, and | |||
all its terms and conditions for copying, distributing or modifying | |||
the Program or works based on it. | |||
6. Each time you redistribute the Program (or any work based on the | |||
Program), the recipient automatically receives a license from the | |||
original licensor to copy, distribute or modify the Program subject to | |||
these terms and conditions. You may not impose any further | |||
restrictions on the recipients' exercise of the rights granted herein. | |||
You are not responsible for enforcing compliance by third parties to | |||
this License. | |||
7. If, as a consequence of a court judgment or allegation of patent | |||
infringement or for any other reason (not limited to patent issues), | |||
conditions are imposed on you (whether by court order, agreement or | |||
otherwise) that contradict the conditions of this License, they do not | |||
excuse you from the conditions of this License. If you cannot | |||
distribute so as to satisfy simultaneously your obligations under this | |||
License and any other pertinent obligations, then as a consequence you | |||
may not distribute the Program at all. For example, if a patent | |||
license would not permit royalty-free redistribution of the Program by | |||
all those who receive copies directly or indirectly through you, then | |||
the only way you could satisfy both it and this License would be to | |||
refrain entirely from distribution of the Program. | |||
If any portion of this section is held invalid or unenforceable under | |||
any particular circumstance, the balance of the section is intended to | |||
apply and the section as a whole is intended to apply in other | |||
circumstances. | |||
It is not the purpose of this section to induce you to infringe any | |||
patents or other property right claims or to contest validity of any | |||
such claims; this section has the sole purpose of protecting the | |||
integrity of the free software distribution system, which is | |||
implemented by public license practices. Many people have made | |||
generous contributions to the wide range of software distributed | |||
through that system in reliance on consistent application of that | |||
system; it is up to the author/donor to decide if he or she is willing | |||
to distribute software through any other system and a licensee cannot | |||
impose that choice. | |||
This section is intended to make thoroughly clear what is believed to | |||
be a consequence of the rest of this License. | |||
8. If the distribution and/or use of the Program is restricted in | |||
certain countries either by patents or by copyrighted interfaces, the | |||
original copyright holder who places the Program under this License | |||
may add an explicit geographical distribution limitation excluding | |||
those countries, so that distribution is permitted only in or among | |||
countries not thus excluded. In such case, this License incorporates | |||
the limitation as if written in the body of this License. | |||
9. The Free Software Foundation may publish revised and/or new versions | |||
of the General Public License from time to time. Such new versions will | |||
be similar in spirit to the present version, but may differ in detail to | |||
address new problems or concerns. | |||
Each version is given a distinguishing version number. If the Program | |||
specifies a version number of this License which applies to it and "any | |||
later version", you have the option of following the terms and conditions | |||
either of that version or of any later version published by the Free | |||
Software Foundation. If the Program does not specify a version number of | |||
this License, you may choose any version ever published by the Free Software | |||
Foundation. | |||
10. If you wish to incorporate parts of the Program into other free | |||
programs whose distribution conditions are different, write to the author | |||
to ask for permission. For software which is copyrighted by the Free | |||
Software Foundation, write to the Free Software Foundation; we sometimes | |||
make exceptions for this. Our decision will be guided by the two goals | |||
of preserving the free status of all derivatives of our free software and | |||
of promoting the sharing and reuse of software generally. | |||
NO WARRANTY | |||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |||
REPAIR OR CORRECTION. | |||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGES. | |||
END OF TERMS AND CONDITIONS | |||
How to Apply These Terms to Your New Programs | |||
If you develop a new program, and you want it to be of the greatest | |||
possible use to the public, the best way to achieve this is to make it | |||
free software which everyone can redistribute and change under these terms. | |||
To do so, attach the following notices to the program. It is safest | |||
to attach them to the start of each source file to most effectively | |||
convey the exclusion of warranty; and each file should have at least | |||
the "copyright" line and a pointer to where the full notice is found. | |||
<one line to give the program's name and a brief idea of what it does.> | |||
Copyright (C) <year> <name of author> | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
GNU General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program; if not, write to the Free Software | |||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||
Also add information on how to contact you by electronic and paper mail. | |||
If the program is interactive, make it output a short notice like this | |||
when it starts in an interactive mode: | |||
Gnomovision version 69, Copyright (C) year name of author | |||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||
This is free software, and you are welcome to redistribute it | |||
under certain conditions; type `show c' for details. | |||
The hypothetical commands `show w' and `show c' should show the appropriate | |||
parts of the General Public License. Of course, the commands you use may | |||
be called something other than `show w' and `show c'; they could even be | |||
mouse-clicks or menu items--whatever suits your program. | |||
You should also get your employer (if you work as a programmer) or your | |||
school, if any, to sign a "copyright disclaimer" for the program, if | |||
necessary. Here is a sample; alter the names: | |||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |||
`Gnomovision' (which makes passes at compilers) written by James Hacker. | |||
<signature of Ty Coon>, 1 April 1989 | |||
Ty Coon, President of Vice | |||
This General Public License does not permit incorporating your program into | |||
proprietary programs. If your program is a subroutine library, you may | |||
consider it more useful to permit linking proprietary applications with the | |||
library. If this is what you want to do, use the GNU Library General | |||
Public License instead of this License. |
@@ -0,0 +1,96 @@ | |||
= COPS = | |||
COPS stands for Calibre OPDS Php Server. | |||
COPS output is valid the unofficial OPDS validator : | |||
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. | |||
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). | |||
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 a lot of CPU or RAM. | |||
* Not much code. | |||
* Search is available. | |||
* With Dropbox it's very easy to have an up to date OPDS server. | |||
* It was fun to code. | |||
= Prerequisites = | |||
1. PHP 5.3 with GD image processing & SQLite3 support. | |||
2. A web server with PHP support. I only tested with Nginx 1.1.14. For now | |||
there is support for X-Accel-Redirect which is Nginx specific. | |||
3. The path to a calibre library (metadata.db, format, & cover files). | |||
On any Debian base Linux you can use : | |||
aptitude install php5-gd php5-sqlite | |||
= Install = | |||
1. Extract the zip file to a folder in web space (visible to the web server). | |||
2. If a first-time install, copy config_default.php to config_local.php | |||
3. Edit config_local.php to match your config. | |||
In my case I installed COPS in a subdomain. Here is my nginx config file : | |||
server { | |||
listen [::]:80; | |||
server_name opds.mydomain.com; | |||
access_log /var/log/nginx/opds.access.log; | |||
error_log /var/log/nginx/opds.error.log; | |||
root /var/www/opds; | |||
index feed.php; | |||
location ~ \.php$ { | |||
include /etc/nginx/fastcgi_params; | |||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |||
fastcgi_pass unix:/tmp/fcgi.sock; | |||
} | |||
location /Calibre { | |||
root /home/calibre/Dropbox; | |||
internal; | |||
} | |||
} | |||
Beware in this case my Calibre database is in /home/calibre/Dropbox/Calibre/ so | |||
the internal location of nginx has to split like that. | |||
If your Calibre database is inside your web directory then there is no need for | |||
an internal location. | |||
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 : | |||
application/epub+zip epub; | |||
= Known problems = | |||
* Only tested with Nginx. | |||
* Contain Nginx specific code. | |||
* Only works with EPUB (not MOBI or PDF). | |||
* certainly many many more. | |||
= Disclaimer = | |||
I only tested on Debian with Nginx so I have stricly no idea if it works | |||
with Apache or any other web server. | |||
On the OPDS client side I mainly tested with FBReader and Aldiko on Android. | |||
= Copyright & License = | |||
COPS - 2012 (c) Sébastien Lucas <sebastien@slucas.fr> | |||
See COPYING and file headers for license info | |||
@@ -0,0 +1,73 @@ | |||
<?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 Author extends Base { | |||
const ALL_AUTHORS_ID = "calibre:authors"; | |||
public $id; | |||
public $name; | |||
public $sort; | |||
public function __construct($pid, $pname) { | |||
$this->id = $pid; | |||
$this->name = $pname; | |||
} | |||
public function getUri () { | |||
return "feed.php?page=".parent::PAGE_AUTHOR_DETAIL."&id=$this->id"; | |||
} | |||
public function getEntryId () { | |||
return self::ALL_AUTHORS_ID.":".$this->id; | |||
} | |||
public static function getCount() { | |||
$nAuthors = parent::getDb ()->query('select count(*) from authors')->fetchColumn(); | |||
parent::addEntryClass ( new Entry ("Authors", self::ALL_AUTHORS_ID, | |||
"Alphabetical index of the $nAuthors authors", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_AUTHORS)))); | |||
} | |||
public static function getAllAuthors() { | |||
$result = parent::getDb ()->query('select authors.id as id, authors.name as name, authors.sort as sort, count(*) as count | |||
from authors, books_authors_link | |||
where author = authors.id | |||
group by authors.id, authors.name, authors.sort | |||
order by sort'); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$author = new Author ($post->id, $post->sort); | |||
parent::addEntryClass ( new Entry ($post->sort, $author->getEntryId (), | |||
"$post->count books", "text", | |||
array ( new LinkNavigation ($author->getUri ())))); | |||
} | |||
} | |||
public static function getAuthorName ($authorId) { | |||
$result = parent::getDb ()->prepare('select sort from authors where id = ?'); | |||
$result->execute (array ($authorId)); | |||
return $result->fetchColumn (); | |||
} | |||
public static function getAuthorByBookId ($bookId) { | |||
$result = parent::getDb ()->prepare('select authors.id as id, authors.sort as sort | |||
from authors, books_authors_link | |||
where author = authors.id | |||
and book = ?'); | |||
$result->execute (array ($bookId)); | |||
$authorArray = array (); | |||
while ($post = $result->fetchObject ()) { | |||
array_push ($authorArray, new Author ($post->id, $post->sort)); | |||
} | |||
return $authorArray; | |||
} | |||
} | |||
?> |
@@ -0,0 +1,277 @@ | |||
<?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> | |||
*/ | |||
class Link | |||
{ | |||
public $href; | |||
public $type; | |||
public $rel; | |||
public $title; | |||
public function __construct($phref, $ptype, $prel = NULL, $ptitle = NULL) { | |||
$this->href = $phref; | |||
$this->type = $ptype; | |||
$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 | |||
{ | |||
const OPDS_NAVIGATION_TYPE = "application/atom+xml;profile=opds-catalog;kind=navigation"; | |||
public function __construct($phref, $prel = NULL, $ptitle = NULL) { | |||
parent::__construct ($phref, self::OPDS_NAVIGATION_TYPE, $prel, $ptitle); | |||
} | |||
} | |||
class Entry | |||
{ | |||
public $title; | |||
public $id; | |||
public $content; | |||
public $contentType; | |||
public $linkArray; | |||
public $localUpdated; | |||
private static $updated = NULL; | |||
public function getUpdatedTime () { | |||
if (!is_null ($this->localUpdated)) { | |||
return date (DATE_ATOM, $this->localUpdated); | |||
} | |||
if (is_null (self::$updated)) { | |||
self::$updated = time(); | |||
} | |||
return date (DATE_ATOM, self::$updated); | |||
} | |||
public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray) { | |||
$this->title = $ptitle; | |||
$this->id = $pid; | |||
$this->content = $pcontent; | |||
$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 | |||
{ | |||
public $book; | |||
public function __construct($ptitle, $pid, $pcontent, $pcontentType, $plinkArray, $pbook) { | |||
parent::__construct ($ptitle, $pid, $pcontent, $pcontentType, $plinkArray); | |||
$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 (); | |||
} | |||
} | |||
/* Polymorphism is strange with PHP */ | |||
public function render ($xml) { | |||
$xml->startElement ("entry"); | |||
self::renderContent ($xml); | |||
$xml->endElement (); | |||
} | |||
} | |||
abstract class Base | |||
{ | |||
const PAGE_INDEX = "index"; | |||
const PAGE_ALL_AUTHORS = "1"; | |||
const PAGE_AUTHORS_FIRST_LETTER = "2"; | |||
const PAGE_AUTHOR_DETAIL = "3"; | |||
const PAGE_ALL_BOOKS = "4"; | |||
const PAGE_ALL_BOOKS_LETTER = "5"; | |||
const PAGE_ALL_SERIES = "6"; | |||
const PAGE_SERIE_DETAIL = "7"; | |||
const PAGE_OPENSEARCH = "8"; | |||
const PAGE_OPENSEARCH_QUERY = "9"; | |||
const PAGE_ALL_RECENT_BOOKS = "10"; | |||
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; | |||
if (is_null (self::$db)) { | |||
try { | |||
self::$db = new PDO('sqlite:'. $config['calibre_directory'] .'metadata.db'); | |||
} catch (Exception $e) { | |||
echo $e; | |||
die($e); | |||
} | |||
} | |||
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); | |||
} | |||
} | |||
?> |
@@ -0,0 +1,263 @@ | |||
<?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 Book extends Base { | |||
const ALL_BOOKS_ID = "calibre:books"; | |||
const ALL_RECENT_BOOKS_ID = "calibre:recentbooks"; | |||
public $id; | |||
public $title; | |||
public $timestamp; | |||
public $pubdate; | |||
public $path; | |||
public $relativePath; | |||
public $seriesIndex; | |||
public $comment; | |||
public $authors = NULL; | |||
public $serie = NULL; | |||
public $tags = NULL; | |||
public function __construct($pid, $ptitle, $ptimestamp, $ppubdate, $ppath, $pseriesIndex, $pcomment) { | |||
global $config; | |||
$this->id = $pid; | |||
$this->title = $ptitle; | |||
$this->timestamp = strtotime ($ptimestamp); | |||
$this->pubdate = strtotime ($ppubdate); | |||
$this->path = $config['calibre_directory'] . $ppath; | |||
$this->relativePath = $ppath; | |||
$this->seriesIndex = $pseriesIndex; | |||
$this->comment = $pcomment; | |||
} | |||
public function getEntryId () { | |||
return self::ALL_BOOKS_ID.":".$this->id; | |||
} | |||
public function getTitle () { | |||
return $this->title; | |||
} | |||
public function getAuthors () { | |||
if (is_null ($this->authors)) { | |||
$this->authors = Author::getAuthorByBookId ($this->id); | |||
} | |||
return $this->authors; | |||
} | |||
public function getSerie () { | |||
if (is_null ($this->serie)) { | |||
$this->serie = Serie::getSerieByBookId ($this->id); | |||
} | |||
return $this->serie; | |||
} | |||
public function getTags () { | |||
if (is_null ($this->tags)) { | |||
$this->tags = array (); | |||
$result = parent::getDb ()->prepare('select name | |||
from books_tags_link, tags | |||
where tag = tags.id | |||
and book = ? | |||
order by name'); | |||
$result->execute (array ($this->id)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
array_push ($this->tags, $post->name); | |||
} | |||
} | |||
return $this->tags; | |||
} | |||
public function getComment () { | |||
$addition = ""; | |||
$se = $this->getSerie (); | |||
if (!is_null ($se)) { | |||
$addition = $addition . "<strong>Series : </strong>Book $this->seriesIndex in $se->name<br />\n"; | |||
} | |||
return $addition . strip_tags ($this->comment, '<div>'); | |||
} | |||
public function getFilePath ($extension, $relative = false) | |||
{ | |||
if ($handle = opendir($this->path)) { | |||
while (false !== ($file = readdir($handle))) { | |||
if (preg_match ('/' . $extension . '$/', $file)) { | |||
if ($relative) | |||
{ | |||
return $this->relativePath."/".$file; | |||
} | |||
else | |||
{ | |||
return $this->path."/".$file; | |||
} | |||
} | |||
} | |||
} | |||
return NULL; | |||
} | |||
public function getLinkArray () | |||
{ | |||
global $config; | |||
$linkArray = array(); | |||
if ($handle = opendir($this->path)) { | |||
while (false !== ($file = readdir($handle))) { | |||
if (preg_match ('/jpg$/', $file)) { | |||
if (preg_match ('/^\//', $config['calibre_directory'])) | |||
{ | |||
array_push ($linkArray, new Link ("fetch.php?id=$this->id", "image/jpeg", "http://opds-spec.org/image")); | |||
} | |||
else | |||
{ | |||
array_push ($linkArray, new Link (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")); | |||
} | |||
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 ($this->getAuthors () as $author) { | |||
array_push ($linkArray, new LinkNavigation ($author->getUri (), "related", "Other books by $author->name")); | |||
} | |||
$serie = $this->getSerie (); | |||
if (!is_null ($serie)) { | |||
array_push ($linkArray, new LinkNavigation ($serie->getUri (), "related", "Other books by the serie $serie->name")); | |||
} | |||
return $linkArray; | |||
} | |||
public function getEntry () { | |||
parent::addEntryClass (new EntryBook ($this->getTitle (), $this->getEntryId (), | |||
$this->getComment (), "text/html", | |||
$this->getLinkArray (), $this)); | |||
} | |||
public static function getCount() { | |||
$nBooks = parent::getDb ()->query('select count(*) from books')->fetchColumn(); | |||
parent::addEntryClass (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", | |||
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)))); | |||
} | |||
public static function getBooksByAuthor($authorId) { | |||
$result = parent::getDb ()->prepare('select books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index | |||
from books_authors_link, books left outer join comments on comments.book = books.id | |||
where books_authors_link.book = books.id | |||
and author = ? | |||
order by pubdate'); | |||
$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 (); | |||
} | |||
} | |||
public static function getBooksBySeries($serieId) { | |||
$result = parent::getDb ()->prepare('select books.id as id, books.title as title, text as comment, path, timestamp, pubdate, series_index | |||
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'); | |||
$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 (); | |||
} | |||
} | |||
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 = ?'); | |||
$result->execute (array ($bookId)); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
return $book; | |||
} | |||
return NULL; | |||
} | |||
public static function getBooksByQuery($query) { | |||
$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 exists (select null from authors, books_authors_link where book = books.id and author = authors.id and authors.name like ?) | |||
or title like ?"); | |||
$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 (); | |||
} | |||
} | |||
public static function getAllBooks() { | |||
$result = parent::getDb ()->query("select substr (upper (sort), 1, 1) as title, count(*) as count | |||
from books | |||
group by substr (upper (sort), 1, 1) | |||
order by substr (upper (sort), 1, 1)"); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
parent::addEntryClass (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)))); | |||
} | |||
} | |||
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 ?'); | |||
$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 (); | |||
} | |||
} | |||
public static function getAllRecentBooks() { | |||
global $config; | |||
$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']); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
$book = new Book ($post->id, $post->title, $post->timestamp, $post->pubdate, $post->path, $post->series_index, $post->comment); | |||
$book->getEntry (); | |||
} | |||
} | |||
} | |||
?> |
@@ -0,0 +1,12 @@ | |||
<?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 'config_default.php'; | |||
if (file_exists('config_local.php')) | |||
require_once 'config_local.php'; | |||
?> |
@@ -0,0 +1,34 @@ | |||
<?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> | |||
*/ | |||
$config = array(); | |||
/* | |||
* The directory containing calibre's metadata.db file, with sub-directories | |||
* containing all the formats. | |||
* If this directory starts with a / EPUB download will only work with Nginx | |||
* and if the calibre_internal_directory is set | |||
*/ | |||
$config['calibre_directory'] = './'; | |||
/* | |||
* The internal directory set in nginx config file | |||
*/ | |||
$config['calibre_internal_directory'] = '/Calibre/'; | |||
/* | |||
* Number of books | |||
*/ | |||
$config['cops_recentbooks_limit'] = '50'; | |||
/* | |||
* The internal directory set in nginx config file | |||
*/ | |||
$config['cops_title_default'] = "Sebastien's COPS"; | |||
?> |
@@ -0,0 +1,86 @@ | |||
<?php | |||
/** | |||
* COPS (Calibre OPDS PHP Server) main script | |||
* | |||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html) | |||
* @author Sébastien Lucas <sebastien@slucas.fr> | |||
* | |||
*/ | |||
require_once ("config.php"); | |||
require_once ("base.php"); | |||
require_once ("author.php"); | |||
require_once ("serie.php"); | |||
require_once ("book.php"); | |||
header ("Content-Type:application/xml"); | |||
$page = Base::PAGE_INDEX; | |||
global $config; | |||
if (!empty ($_GET) && isset($_GET["page"])) { | |||
$page = $_GET["page"]; | |||
} | |||
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 (); | |||
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(); | |||
break; | |||
} | |||
echo Base::endXmlDocument (); | |||
?> |
@@ -0,0 +1,58 @@ | |||
<?php | |||
/** | |||
* COPS (Calibre OPDS PHP Server) | |||
* | |||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html) | |||
* @author Gordon Page <gordon@incero.com> with integration/modification by Sébastien Lucas <sebastien@slucas.fr> | |||
*/ | |||
require_once ("config.php"); | |||
require_once('book.php'); | |||
global $config; | |||
$bookId = $_GET["id"]; | |||
$book = Book::getBookById($bookId); | |||
$type = "jpg"; | |||
if (!empty ($_GET) && isset($_GET["type"])) { | |||
$type = $_GET["type"]; | |||
} | |||
switch ($type) | |||
{ | |||
case "jpg": | |||
header("Content-type: image/jpeg"); | |||
if (isset($_GET["width"])) | |||
{ | |||
$file = $book->getFilePath ($type); | |||
// get image size | |||
if($size = GetImageSize($file)){ | |||
$w = $size[0]; | |||
$h = $size[1]; | |||
//set new size | |||
$nw = $_GET["width"]; | |||
$nh = ($nw*$h)/$w; | |||
} | |||
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; | |||
case "epub": | |||
header("Content-type: application/epub+zip"); | |||
break; | |||
} | |||
$file = $book->getFilePath ($type, true); | |||
header('Content-Disposition: attachement; filename="' . basename ($file) . '"'); | |||
header ("X-Accel-Redirect: " . $config['calibre_internal_directory'] . $file); | |||
?> |
@@ -0,0 +1,67 @@ | |||
<?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 Serie extends Base { | |||
const ALL_SERIES_ID = "calibre:series"; | |||
public $id; | |||
public $name; | |||
public function __construct($pid, $pname) { | |||
$this->id = $pid; | |||
$this->name = $pname; | |||
} | |||
public function getUri () { | |||
return "feed.php?page=".parent::PAGE_SERIE_DETAIL."&id=$this->id"; | |||
} | |||
public static function getCount() { | |||
$nSeries = parent::getDb ()->query('select count(*) from series')->fetchColumn(); | |||
parent::addEntryClass (new Entry ("Series", self::ALL_SERIES_ID, | |||
"Alphabetical index of the $nSeries series", "text", | |||
array ( new LinkNavigation ("feed.php?page=".parent::PAGE_ALL_SERIES)))); | |||
} | |||
public static function getSerieByBookId ($bookId) { | |||
$result = parent::getDb ()->prepare('select series.id as id, name | |||
from books_series_link, series | |||
where series.id = series and book = ?'); | |||
$result->execute (array ($bookId)); | |||
if ($post = $result->fetchObject ()) { | |||
return new Serie ($post->id, $post->name); | |||
} | |||
return NULL; | |||
} | |||
public static function getSerieById ($serieId) { | |||
$result = parent::getDb ()->prepare('select id, name from series where id = ?'); | |||
$result->execute (array ($serieId)); | |||
if ($post = $result->fetchObject ()) { | |||
return new Serie ($post->id, $post->name); | |||
} | |||
return NULL; | |||
} | |||
public static function getAllSeries() { | |||
$result = parent::getDb ()->query('select series.id as id, series.name as name, series.sort as sort, count(*) as count | |||
from series, books_series_link | |||
where series.id = series | |||
group by series.id, series.name, series.sort | |||
order by series.sort'); | |||
while ($post = $result->fetchObject ()) | |||
{ | |||
parent::addEntryClass (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")))); | |||
} | |||
} | |||
} | |||
?> |