@@ -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")))); | |||||
} | |||||
} | |||||
} | |||||
?> |