Merge pull request #93 from Marsender/master

Création de bases Calibre pour COPS
This commit is contained in:
Sébastien Lucas 2013-09-15 00:24:44 -07:00
commit 2bcd38f4f1
9 changed files with 1460 additions and 29 deletions

View file

@ -157,12 +157,12 @@ function localize($phrase, $count=-1) {
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
} }
$lang_file_en = NULL; $lang_file_en = NULL;
$lang_file = 'lang/Localization_' . $lang . '.json'; $lang_file = dirname(__FILE__). '/lang/Localization_' . $lang . '.json';
if (!file_exists($lang_file)) { if (!file_exists($lang_file)) {
$lang_file = 'lang/' . 'Localization_en.json'; $lang_file = dirname(__FILE__). '/lang/' . 'Localization_en.json';
} }
elseif ($lang != "en") { elseif ($lang != "en") {
$lang_file_en = 'lang/' . 'Localization_en.json'; $lang_file_en = dirname(__FILE__). '/lang/' . 'Localization_en.json';
} }
$lang_file_content = file_get_contents($lang_file); $lang_file_content = file_get_contents($lang_file);
/* Load the language file as a JSON object and transform it into an associative array */ /* Load the language file as a JSON object and transform it into an associative array */

View file

@ -0,0 +1,209 @@
<?php
/**
* BaseExport class
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Didier Corbière <didier.corbiere@opale-concept.com>
*/
class BaseExport
{
protected $mProperties = null;
protected $mFileName = '';
protected $mSearch = null;
protected $mReplace = null;
public $mFormatProperty = true;
/**
* Open an export file (or create if file does not exist)
*
* @param string Export file name
* @param boolean Force file creation
*/
public function __construct($inFileName, $inCreate = false)
{
if ($inCreate && file_exists($inFileName)) {
if (!unlink($inFileName)) {
$error = sprintf('Cannot remove file: %s', $inFileName);
throw new Exception($error);
}
}
$this->mFileName = $inFileName;
$this->mProperties = array();
}
public function ClearProperties()
{
$this->mProperties = array();
}
public function SetProperty($inKey, $inValue)
{
// Don't store empty keys
if (empty($inKey)) {
return;
}
if ($this->mFormatProperty) {
$inValue = $this->FormatProperty($inValue);
}
$this->mProperties[$inKey] = $inValue;
}
/**
* Format a property
*
* @param string or array of strings to format
* @return string or array of strings formated
*/
protected function FormatProperty($inValue)
{
if (!isset($inValue)) {
return '';
}
if (is_numeric($inValue)) {
return (string)$inValue;
}
if (is_array($inValue)) {
// Recursive call for arrays
foreach ($inValue as $key => $value) {
$inValue[$key] = $this->FormatProperty($value);
}
return $inValue;
}
if (!is_string($inValue) || empty($inValue)) {
return '';
}
// Replace html entities with normal characters
$str = html_entity_decode($inValue, ENT_COMPAT, 'UTF-8');
// Replace characters
if (isset($this->mSearch)) {
$str = str_replace($this->mSearch, $this->mReplace, $str);
}
// Strip double spaces
while (strpos($str, ' ') !== false) {
$str = str_replace(' ', ' ', $str);
}
// Trim
$str = trim($str);
return $str;
}
/**
* Save data to file
*
* @throws Exception if error
*/
public function SaveToFile()
{
// Write the file
$content = $this->GetContent();
if (!file_put_contents($this->mFileName, $content)) {
$error = sprintf('Cannot save export to file: %s', $this->mFileName);
throw new Exception($error);
}
}
/**
* Send download http headers
*
* @param string $inFileName Download file name to display in the browser
* @param int $inFileSize Download file size
* @param string $inCodeSet Charset
* @throws exception if http headers have been already sent
*
* @return void
*/
private function SendDownloadHeaders($inFileName, $inFileSize = null, $inCodeSet = 'utf-8')
{
// Throws excemtion if http headers have been already sent
$filename = '';
$linenum = 0;
if (headers_sent($filename, $linenum)) {
$error = sprintf('Http headers already sent by file: %s ligne %d', $filename, $linenum);
throw new Exception($error);
}
$inFileName = str_replace(' ', '', basename($inFileName)); // Cleanup file name
$ext = strtolower(substr(strrchr($inFileName, '.'), 1));
switch ($ext) {
case 'pdf':
$contentType = 'application/pdf';
break;
case 'zip':
$contentType = 'application/zip';
break;
case 'xml':
$contentType = 'text/xml';
if (!empty($inCodeSet)) {
$contentType .= '; charset=' . $inCodeSet . '"';
}
break;
case 'txt':
$contentType = 'text/plain';
if (!empty($inCodeSet)) {
$contentType .= '; charset=' . $inCodeSet . '"';
}
break;
case 'csv':
$contentType = 'text/csv';
if (!empty($inCodeSet)) {
$contentType .= '; charset=' . $inCodeSet . '"';
}
break;
case 'html':
$contentType = 'text/html';
if (!empty($inCodeSet)) {
$contentType .= '; charset=' . $inCodeSet . '"';
}
break;
default:
$contentType = 'application/force-download';
break;
}
// Send http headers for download
header('Content-disposition: attachment; filename="' . $inFileName . '"');
Header('Content-Type: ' . $contentType);
//header('Content-Transfer-Encoding: binary');
if (isset($inFileSize)) {
header('Content-Length: ' . $inFileSize);
}
// Send http headers to remove the browser cache
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate("D, d M Y H:i:s") . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
}
/**
* Download export and stop further script execution
*/
public function Download()
{
$content = $this->GetContent();
// Send http download headers
$size = strlen($content);
$this->SendDownloadHeaders($this->mFileName, $size);
// Send file content to download
echo $content;
exit;
}
}
?>

View file

@ -0,0 +1,126 @@
<?php
/**
* BookExport class
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Didier Corbière <didier.corbiere@opale-concept.com>
*/
require_once(realpath(dirname(__FILE__)) . '/CsvExport.class.php');
class BookExport
{
private $mExport = null;
private $mNbBook = 0;
const eExportTypeCsv = 1;
const CsvSeparator = "\t";
/**
* Open an export file (or create if file does not exist)
*
* @param string Export file name
* @param enum Export type
* @param boolean Force file creation
* @throws Exception if error
*/
public function __construct($inFileName, $inExportType, $inCreate = false)
{
switch ($inExportType) {
case self::eExportTypeCsv:
$this->mExport = new CsvExport($inFileName);
break;
default:
$error = sprintf('Incorrect export type: %d', $inExportType);
throw new Exception($error);
}
}
/**
* Add an epub to the export
*
* @param string Epub file name
* @throws Exception if error
*
* @return void
*/
public function AddEpub($inFileName)
{
// Load the book infos
$bookInfos = new BookInfos();
$bookInfos->LoadFromEpub($inFileName);
// Add the book
$this->AddBook($bookInfos);
}
/**
* Add a new book to the export
*
* @param object BookInfo object
* @throws Exception if error
*
* @return void
*/
private function AddBook($inBookInfo)
{
// Add export header
if ($this->mNbBook++ == 0) {
$i = 1;
$this->mExport->SetProperty($i++, 'Format');
$this->mExport->SetProperty($i++, 'Path');
$this->mExport->SetProperty($i++, 'Name');
$this->mExport->SetProperty($i++, 'Uuid');
$this->mExport->SetProperty($i++, 'Uri');
$this->mExport->SetProperty($i++, 'Title');
$this->mExport->SetProperty($i++, 'Authors');
$this->mExport->SetProperty($i++, 'AuthorsSort');
$this->mExport->SetProperty($i++, 'Language');
$this->mExport->SetProperty($i++, 'Description');
$this->mExport->SetProperty($i++, 'Subjects');
$this->mExport->SetProperty($i++, 'Cover');
$this->mExport->SetProperty($i++, 'Isbn');
$this->mExport->SetProperty($i++, 'Rights');
$this->mExport->SetProperty($i++, 'Publisher');
$this->mExport->AddContent();
}
// Add book infos to the export
$i = 1;
$this->mExport->SetProperty($i++, $inBookInfo->mFormat);
$this->mExport->SetProperty($i++, $inBookInfo->mPath);
$this->mExport->SetProperty($i++, $inBookInfo->mName);
$this->mExport->SetProperty($i++, $inBookInfo->mUuid);
$this->mExport->SetProperty($i++, $inBookInfo->mUri);
$this->mExport->SetProperty($i++, $inBookInfo->mTitle);
$this->mExport->SetProperty($i++, implode(' - ', $inBookInfo->mAuthors));
$this->mExport->SetProperty($i++, implode(' - ', array_keys($inBookInfo->mAuthors)));
$this->mExport->SetProperty($i++, $inBookInfo->mLanguage);
$this->mExport->SetProperty($i++, $inBookInfo->mDescription);
$this->mExport->SetProperty($i++, implode(' - ', $inBookInfo->mSubjects));
$this->mExport->SetProperty($i++, $inBookInfo->mCover);
$this->mExport->SetProperty($i++, $inBookInfo->mIsbn);
$this->mExport->SetProperty($i++, $inBookInfo->mRights);
$this->mExport->SetProperty($i++, $inBookInfo->mPublisher);
$this->mExport->AddContent();
}
/**
* Download export and stop further script execution
*/
public function Download()
{
$this->mExport->Download();
}
/**
* Save export to file
*/
public function SaveToFile()
{
$this->mExport->SaveToFile();
}
}
?>

View file

@ -0,0 +1,65 @@
<?php
/**
* BookInfos class
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Didier Corbière <didier.corbiere@opale-concept.com>
*/
require_once(realpath(dirname(__FILE__)) . '/ZipFile.class.php');
require_once(realpath(dirname(dirname(__FILE__))) . '/php-epub-meta/epub.php');
/**
* BookInfos class contains informations about a book,
* and methods to load this informations from multiple sources (eg epub file)
*/
class BookInfos
{
public $mPath = '';
public $mName = '';
public $mFormat = '';
public $mUuid = '';
public $mUri = '';
public $mTitle = '';
public $mAuthors = null;
public $mLanguage = '';
public $mDescription = '';
public $mSubjects = null;
public $mCover = '';
public $mIsbn = '';
public $mRights = '';
public $mPublisher = '';
/**
* Loads book infos from an epub file
*
* @param string Epub full file name
* @throws Exception if error
*
* @return void
*/
public function LoadFromEpub($inFileName)
{
// Load the epub file
$epub = new EPub($inFileName, 'ZipFile');
// Get the epub infos
$this->mFormat = 'epub';
$this->mPath = pathinfo($inFileName, PATHINFO_DIRNAME);
$this->mName = pathinfo($inFileName, PATHINFO_FILENAME);
$this->mUuid = $epub->Uuid();
$this->mUri = $epub->Uri();
$this->mTitle = $epub->Title();
$this->mAuthors = $epub->Authors();
$this->mLanguage = $epub->Language();
$this->mDescription = $epub->Description();
$this->mSubjects = $epub->Subjects();
$this->mCover = $epub->getCoverItem();
$this->mIsbn = $epub->ISBN();
$this->mRights = $epub->Copyright();
$this->mPublisher = $epub->Publisher();
}
}
?>

View file

@ -0,0 +1,254 @@
<?php
/**
* CalibreDbLoader class
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Didier Corbière <didier.corbiere@opale-concept.com>
*/
require_once(realpath(dirname(__FILE__)) . '/BookInfos.class.php');
/**
* Calibre database sql file that comes unmodified from Calibre project:
* /calibre/resources/metadata_sqlite.sql
*/
define('CalibreCreateDbSql', realpath(dirname(__FILE__)) . '/metadata_sqlite.sql');
/**
* CalibreDbLoader class allows to open or create a new Calibre database,
* and then add BookInfos objects into the database
*/
class CalibreDbLoader
{
private $mDb = null;
/**
* Open a Calibre database (or create if database does not exist)
*
* @param string Calibre database file name
* @param boolean Force database creation
*/
public function __construct($inDbFileName, $inCreate = false)
{
if ($inCreate || !file_exists($inDbFileName)) {
$this->CreateDatabase($inDbFileName);
}
else {
$this->OpenDatabase($inDbFileName);
}
}
/**
* Create an sqlite database
*
* @param string Database file name
* @throws Exception if error
*
* @return void
*/
private function CreateDatabase($inDbFileName)
{
// Read the sql file
$content = file_get_contents(CalibreCreateDbSql);
if ($content === false) {
$error = sprintf('Cannot read sql file: %s', $inDbFileName);
throw new Exception($error);
}
// Remove the database file
if (file_exists($inDbFileName) && !unlink($inDbFileName)) {
$error = sprintf('Cannot remove database file: %s', $inDbFileName);
throw new Exception($error);
}
// Create the new database file
$this->OpenDatabase($inDbFileName);
// Create the database tables
try {
$sqlArray = explode('CREATE ', $content);
foreach ($sqlArray as $sql) {
$sql = trim($sql);
if (empty($sql)) {
continue;
}
$sql = 'CREATE ' . $sql;
$str = strtolower($sql);
if (strpos($str, 'create view') !== false) {
continue;
}
if (strpos($str, 'title_sort') !== false) {
continue;
}
$stmt = $this->mDb->prepare($sql);
$stmt->execute();
}
}
catch (Exception $e) {
$error = sprintf('Cannot create database: %s', $e->getMessage());
throw new Exception($error);
}
}
/**
* Open an sqlite database
*
* @param string Database file name
* @throws Exception if error
*
* @return void
*/
private function OpenDatabase($inDbFileName)
{
try {
// Init the Data Source Name
$dsn = 'sqlite:' . $inDbFileName;
// Open the database
$this->mDb = new PDO($dsn); // Send an exception if error
$this->mDb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//echo sprintf('Init database ok for: %s%s', $dsn, '<br />');
}
catch (Exception $e) {
$error = sprintf('Cannot open database [%s]: %s', $dsn, $e->getMessage());
throw new Exception($error);
}
}
/**
* Add an epub to the db
*
* @param string Epub file name
* @throws Exception if error
*
* @return void
*/
public function AddEpub($inFileName)
{
// Load the book infos
$bookInfos = new BookInfos();
$bookInfos->LoadFromEpub($inFileName);
// Add the book
$this->AddBook($bookInfos);
}
/**
* Add a new book into the db
*
* @param object BookInfo object
* @throws Exception if error
*
* @return void
*/
private function AddBook($inBookInfo)
{
$sql = 'insert into books(title, sort, uuid, path) values(:title, :sort, :uuid, :path)';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':title', $inBookInfo->mTitle);
$stmt->bindParam(':sort', $inBookInfo->mTitle);
$stmt->bindParam(':uuid', $inBookInfo->mUuid);
$stmt->bindParam(':path', $inBookInfo->mPath);
$stmt->execute();
// Get the book id
$sql = 'select id, title from books where uuid=:uuid';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':uuid', $inBookInfo->mUuid);
$stmt->execute();
$idBook = null;
while ($post = $stmt->fetchObject()) {
if (!isset($idBook)) {
$idBook = $post->id;
}
else {
$error = sprintf('Multiple book id for uuid: %s (already in title "%s")', $inBookInfo->mUuid, $post->title);
throw new Exception($error);
}
}
if (!isset($idBook)) {
$error = sprintf('Cannot find book id for uuid: %s', $inBookInfo->mUuid);
throw new Exception($error);
}
// Add the book formats
$sql = 'insert into data(book, format, name, uncompressed_size) values(:idBook, :format, :name, 0)';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
$stmt->bindParam(':format', $inBookInfo->mFormat);
$stmt->bindParam(':name', $inBookInfo->mName);
$stmt->execute();
// Add the book identifiers
if (!empty($inBookInfo->mUri)) {
$sql = 'insert into identifiers(book, type, val) values(:idBook, :type, :value)';
$stmt = $this->mDb->prepare($sql);
$type = 'URI';
$stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
$stmt->bindParam(':type', $type);
$stmt->bindParam(':value', $inBookInfo->mUri);
$stmt->execute();
}
// Add the authors in the db
foreach ($inBookInfo->mAuthors as $authorSort => $author) {
// Get the author id
$sql = 'select id from authors where name=:author';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':author', $author);
$stmt->execute();
$post = $stmt->fetchObject();
if ($post) {
$idAuthor = $post->id;
}
else {
// Add a new author
$sql = 'insert into authors(name, sort) values(:author, :sort)';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':author', $author);
$stmt->bindParam(':sort', $authorSort);
$stmt->execute();
// Get the author id
$sql = 'select id from authors where name=:author';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':author', $author);
$stmt->execute();
$idAuthor = null;
while ($post = $stmt->fetchObject()) {
if (!isset($idAuthor)) {
$idAuthor = $post->id;
}
else {
$error = sprintf('Multiple authors for name: %s', $author);
throw new Exception($error);
}
}
if (!isset($idAuthor)) {
$error = sprintf('Cannot find author id for name: %s', $author);
throw new Exception($error);
}
// Add the book author link
$sql = 'insert into books_authors_link(book, author) values(:idBook, :idAuthor)';
$stmt = $this->mDb->prepare($sql);
$stmt->bindParam(':idBook', $idBook, PDO::PARAM_INT);
$stmt->bindParam(':idAuthor', $idAuthor, PDO::PARAM_INT);
$stmt->execute();
}
}
}
/**
* Check database for debug
*
* @return void
*/
private function CheckDatabase()
{
// Retrieve some infos for check only
$sql = 'select id, title, sort from books';
$stmt = $this->mDb->prepare($sql);
$stmt->execute();
while ($post = $stmt->fetchObject()) {
$id = $post->id;
$title = $post->title;
$sort = $post->sort;
}
}
}
?>

View file

@ -0,0 +1,77 @@
<?php
/**
* CsvExport class
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Didier Corbière <didier.corbiere@opale-concept.com>
*/
require_once(realpath(dirname(__FILE__)) . '/BaseExport.class.php');
class CsvExport extends BaseExport
{
private $mLines = null;
const CsvSeparator = "\t";
/**
* Open an export file (or create if file does not exist)
*
* @param string Export file name
* @param boolean Force file creation
*/
public function __construct($inFileName)
{
$this->mSearch = array("\r", "\n", self::CsvSeparator);
$this->mReplace = array('', '<br />', '');
// Init container
$this->mLines = array();
parent::__construct($inFileName);
}
/**
* Add the current properties into the export content
* and reset the properties
*/
public function AddContent()
{
$text = '';
foreach ($this->mProperties as $key => $value) {
$info = '';
if (is_array($value)) {
foreach ($value as $value1) {
// Escape quotes
if (strpos($value1, '\'') !== false) {
$value1 = '\'' . str_replace('\'', '\'\'', $value1) . '\'';
}
$text .= $value1 . self::CsvSeparator;
}
continue;
}
else {
// Escape quotes
if (strpos($value, '\'') !== false) {
$value = '\'' . str_replace('\'', '\'\'', $value) . '\'';
}
$info = $value;
}
$text .= $info . self::CsvSeparator;
}
$this->mLines[] = $text;
$this->ClearProperties();
}
protected function GetContent()
{
$text = implode("\n", $this->mLines) . "\n";
return $text;
}
}
?>

View file

@ -0,0 +1,119 @@
<?php
/**
* ZipFile class
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Didier Corbière <didier.corbiere@opale-concept.com>
*/
/**
* ZipFile class allows to open files inside a zip file with the standard php zip functions
*/
class ZipFile
{
private $mZip;
private $mEntries;
public function __construct()
{
$this->mZip = null;
$this->mEntries = null;
}
/**
* Destructor
*/
public function __destruct()
{
$this->Close();
}
/**
* Open a zip file and read it's entries
*
* @param string $inFileName
* @return boolean True if zip file has been correctly opended, else false
*/
public function Open($inFileName)
{
$this->Close();
$this->mZip = zip_open($inFileName);
if (!$this->mZip) {
return false;
}
$this->mEntries = array();
while ($entry = zip_read($this->mZip)) {
$fileName = zip_entry_name($entry);
$this->mEntries[$fileName] = $entry;
}
return true;
}
/**
* Check if a file exist in the zip entries
*
* @param string $inFileName File to search
*
* @return boolean True if the file exist, else false
*/
public function FileExists($inFileName)
{
if (!isset($this->mZip)) {
return false;
}
if (!isset($this->mEntries[$inFileName])) {
return false;
}
return true;
}
/**
* Read the content of a file in the zip entries
*
* @param string $inFileName File to search
*
* @return mixed File content the file exist, else false
*/
public function FileRead($inFileName)
{
if (!isset($this->mZip)) {
return false;
}
if (!isset($this->mEntries[$inFileName])) {
return false;
}
$entry = $this->mEntries[$inFileName];
if (!zip_entry_open($this->mZip, $entry)) {
return false;
}
$data = zip_entry_read($entry, zip_entry_filesize($entry));
zip_entry_close($entry);
return $data;
}
/**
* Close the zip file
*
* @return void
*/
public function Close()
{
if (!isset($this->mZip)) {
return false;
}
zip_close($this->mZip);
}
}
?>

View file

@ -0,0 +1,549 @@
CREATE TABLE authors ( id INTEGER PRIMARY KEY,
name TEXT NOT NULL COLLATE NOCASE,
sort TEXT COLLATE NOCASE,
link TEXT NOT NULL DEFAULT "",
UNIQUE(name)
);
CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT 'Unknown' COLLATE NOCASE,
sort TEXT COLLATE NOCASE,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
pubdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
series_index REAL NOT NULL DEFAULT 1.0,
author_sort TEXT COLLATE NOCASE,
isbn TEXT DEFAULT "" COLLATE NOCASE,
lccn TEXT DEFAULT "" COLLATE NOCASE,
path TEXT NOT NULL DEFAULT "",
flags INTEGER NOT NULL DEFAULT 1,
uuid TEXT,
has_cover BOOL DEFAULT 0,
last_modified TIMESTAMP NOT NULL DEFAULT "2000-01-01 00:00:00+00:00");
CREATE TABLE books_authors_link ( id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
author INTEGER NOT NULL,
UNIQUE(book, author)
);
CREATE TABLE books_languages_link ( id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
lang_code INTEGER NOT NULL,
item_order INTEGER NOT NULL DEFAULT 0,
UNIQUE(book, lang_code)
);
CREATE TABLE books_plugin_data(id INTEGER PRIMARY KEY,
book INTEGER NON NULL,
name TEXT NON NULL,
val TEXT NON NULL,
UNIQUE(book,name));
CREATE TABLE books_publishers_link ( id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
publisher INTEGER NOT NULL,
UNIQUE(book)
);
CREATE TABLE books_ratings_link ( id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
rating INTEGER NOT NULL,
UNIQUE(book, rating)
);
CREATE TABLE books_series_link ( id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
series INTEGER NOT NULL,
UNIQUE(book)
);
CREATE TABLE books_tags_link ( id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
tag INTEGER NOT NULL,
UNIQUE(book, tag)
);
CREATE TABLE comments ( id INTEGER PRIMARY KEY,
book INTEGER NON NULL,
text TEXT NON NULL COLLATE NOCASE,
UNIQUE(book)
);
CREATE TABLE conversion_options ( id INTEGER PRIMARY KEY,
format TEXT NOT NULL COLLATE NOCASE,
book INTEGER,
data BLOB NOT NULL,
UNIQUE(format,book)
);
CREATE TABLE custom_columns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT NOT NULL,
name TEXT NOT NULL,
datatype TEXT NOT NULL,
mark_for_delete BOOL DEFAULT 0 NOT NULL,
editable BOOL DEFAULT 1 NOT NULL,
display TEXT DEFAULT "{}" NOT NULL,
is_multiple BOOL DEFAULT 0 NOT NULL,
normalized BOOL NOT NULL,
UNIQUE(label)
);
CREATE TABLE data ( id INTEGER PRIMARY KEY,
book INTEGER NON NULL,
format TEXT NON NULL COLLATE NOCASE,
uncompressed_size INTEGER NON NULL,
name TEXT NON NULL,
UNIQUE(book, format)
);
CREATE TABLE feeds ( id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
script TEXT NOT NULL,
UNIQUE(title)
);
CREATE TABLE identifiers ( id INTEGER PRIMARY KEY,
book INTEGER NON NULL,
type TEXT NON NULL DEFAULT "isbn" COLLATE NOCASE,
val TEXT NON NULL COLLATE NOCASE,
UNIQUE(book, type)
);
CREATE TABLE languages ( id INTEGER PRIMARY KEY,
lang_code TEXT NON NULL COLLATE NOCASE,
UNIQUE(lang_code)
);
CREATE TABLE library_id ( id INTEGER PRIMARY KEY,
uuid TEXT NOT NULL,
UNIQUE(uuid)
);
CREATE TABLE metadata_dirtied(id INTEGER PRIMARY KEY,
book INTEGER NOT NULL,
UNIQUE(book));
CREATE TABLE preferences(id INTEGER PRIMARY KEY,
key TEXT NON NULL,
val TEXT NON NULL,
UNIQUE(key));
CREATE TABLE publishers ( id INTEGER PRIMARY KEY,
name TEXT NOT NULL COLLATE NOCASE,
sort TEXT COLLATE NOCASE,
UNIQUE(name)
);
CREATE TABLE ratings ( id INTEGER PRIMARY KEY,
rating INTEGER CHECK(rating > -1 AND rating < 11),
UNIQUE (rating)
);
CREATE TABLE series ( id INTEGER PRIMARY KEY,
name TEXT NOT NULL COLLATE NOCASE,
sort TEXT COLLATE NOCASE,
UNIQUE (name)
);
CREATE TABLE tags ( id INTEGER PRIMARY KEY,
name TEXT NOT NULL COLLATE NOCASE,
UNIQUE (name)
);
CREATE VIEW meta AS
SELECT id, title,
(SELECT sortconcat(bal.id, name) FROM books_authors_link AS bal JOIN authors ON(author = authors.id) WHERE book = books.id) authors,
(SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher,
(SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating,
timestamp,
(SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size,
(SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags,
(SELECT text FROM comments WHERE book=books.id) comments,
(SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series,
series_index,
sort,
author_sort,
(SELECT concat(format) FROM data WHERE data.book=books.id) formats,
isbn,
path,
lccn,
pubdate,
flags,
uuid
FROM books;
CREATE VIEW tag_browser_authors AS SELECT
id,
name,
(SELECT COUNT(id) FROM books_authors_link WHERE author=authors.id) count,
(SELECT AVG(ratings.rating)
FROM books_authors_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.author=authors.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
sort AS sort
FROM authors;
CREATE VIEW tag_browser_filtered_authors AS SELECT
id,
name,
(SELECT COUNT(books_authors_link.id) FROM books_authors_link WHERE
author=authors.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_authors_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.author=authors.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0 AND
books_list_filter(bl.book)) avg_rating,
sort AS sort
FROM authors;
CREATE VIEW tag_browser_filtered_publishers AS SELECT
id,
name,
(SELECT COUNT(books_publishers_link.id) FROM books_publishers_link WHERE
publisher=publishers.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_publishers_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.publisher=publishers.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0 AND
books_list_filter(bl.book)) avg_rating,
name AS sort
FROM publishers;
CREATE VIEW tag_browser_filtered_ratings AS SELECT
id,
rating,
(SELECT COUNT(books_ratings_link.id) FROM books_ratings_link WHERE
rating=ratings.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_ratings_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.rating=ratings.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0 AND
books_list_filter(bl.book)) avg_rating,
rating AS sort
FROM ratings;
CREATE VIEW tag_browser_filtered_series AS SELECT
id,
name,
(SELECT COUNT(books_series_link.id) FROM books_series_link WHERE
series=series.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_series_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.series=series.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0 AND
books_list_filter(bl.book)) avg_rating,
(title_sort(name)) AS sort
FROM series;
CREATE VIEW tag_browser_filtered_tags AS SELECT
id,
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE
tag=tags.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_tags_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.tag=tags.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0 AND
books_list_filter(bl.book)) avg_rating,
name AS sort
FROM tags;
CREATE VIEW tag_browser_publishers AS SELECT
id,
name,
(SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) count,
(SELECT AVG(ratings.rating)
FROM books_publishers_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.publisher=publishers.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
name AS sort
FROM publishers;
CREATE VIEW tag_browser_ratings AS SELECT
id,
rating,
(SELECT COUNT(id) FROM books_ratings_link WHERE rating=ratings.id) count,
(SELECT AVG(ratings.rating)
FROM books_ratings_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.rating=ratings.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
rating AS sort
FROM ratings;
CREATE VIEW tag_browser_series AS SELECT
id,
name,
(SELECT COUNT(id) FROM books_series_link WHERE series=series.id) count,
(SELECT AVG(ratings.rating)
FROM books_series_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.series=series.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
(title_sort(name)) AS sort
FROM series;
CREATE VIEW tag_browser_tags AS SELECT
id,
name,
(SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) count,
(SELECT AVG(ratings.rating)
FROM books_tags_link AS tl, books_ratings_link AS bl, ratings
WHERE tl.tag=tags.id AND bl.book=tl.book AND
ratings.id = bl.rating AND ratings.rating <> 0) avg_rating,
name AS sort
FROM tags;
CREATE INDEX authors_idx ON books (author_sort COLLATE NOCASE);
CREATE INDEX books_authors_link_aidx ON books_authors_link (author);
CREATE INDEX books_authors_link_bidx ON books_authors_link (book);
CREATE INDEX books_idx ON books (sort COLLATE NOCASE);
CREATE INDEX books_languages_link_aidx ON books_languages_link (lang_code);
CREATE INDEX books_languages_link_bidx ON books_languages_link (book);
CREATE INDEX books_publishers_link_aidx ON books_publishers_link (publisher);
CREATE INDEX books_publishers_link_bidx ON books_publishers_link (book);
CREATE INDEX books_ratings_link_aidx ON books_ratings_link (rating);
CREATE INDEX books_ratings_link_bidx ON books_ratings_link (book);
CREATE INDEX books_series_link_aidx ON books_series_link (series);
CREATE INDEX books_series_link_bidx ON books_series_link (book);
CREATE INDEX books_tags_link_aidx ON books_tags_link (tag);
CREATE INDEX books_tags_link_bidx ON books_tags_link (book);
CREATE INDEX comments_idx ON comments (book);
CREATE INDEX conversion_options_idx_a ON conversion_options (format COLLATE NOCASE);
CREATE INDEX conversion_options_idx_b ON conversion_options (book);
CREATE INDEX custom_columns_idx ON custom_columns (label);
CREATE INDEX data_idx ON data (book);
CREATE INDEX formats_idx ON data (format);
CREATE INDEX languages_idx ON languages (lang_code COLLATE NOCASE);
CREATE INDEX publishers_idx ON publishers (name COLLATE NOCASE);
CREATE INDEX series_idx ON series (name COLLATE NOCASE);
CREATE INDEX tags_idx ON tags (name COLLATE NOCASE);
CREATE TRIGGER books_delete_trg
AFTER DELETE ON books
BEGIN
DELETE FROM books_authors_link WHERE book=OLD.id;
DELETE FROM books_publishers_link WHERE book=OLD.id;
DELETE FROM books_ratings_link WHERE book=OLD.id;
DELETE FROM books_series_link WHERE book=OLD.id;
DELETE FROM books_tags_link WHERE book=OLD.id;
DELETE FROM books_languages_link WHERE book=OLD.id;
DELETE FROM data WHERE book=OLD.id;
DELETE FROM comments WHERE book=OLD.id;
DELETE FROM conversion_options WHERE book=OLD.id;
DELETE FROM books_plugin_data WHERE book=OLD.id;
DELETE FROM identifiers WHERE book=OLD.id;
END;
CREATE TRIGGER books_insert_trg AFTER INSERT ON books
BEGIN
UPDATE books SET sort=title_sort(NEW.title),uuid=uuid4() WHERE id=NEW.id;
END;
CREATE TRIGGER books_update_trg
AFTER UPDATE ON books
BEGIN
UPDATE books SET sort=title_sort(NEW.title)
WHERE id=NEW.id AND OLD.title <> NEW.title;
END;
CREATE TRIGGER fkc_comments_insert
BEFORE INSERT ON comments
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_comments_update
BEFORE UPDATE OF book ON comments
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_data_insert
BEFORE INSERT ON data
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_data_update
BEFORE UPDATE OF book ON data
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_delete_on_authors
BEFORE DELETE ON authors
BEGIN
SELECT CASE
WHEN (SELECT COUNT(id) FROM books_authors_link WHERE author=OLD.id) > 0
THEN RAISE(ABORT, 'Foreign key violation: authors is still referenced')
END;
END;
CREATE TRIGGER fkc_delete_on_languages
BEFORE DELETE ON languages
BEGIN
SELECT CASE
WHEN (SELECT COUNT(id) FROM books_languages_link WHERE lang_code=OLD.id) > 0
THEN RAISE(ABORT, 'Foreign key violation: language is still referenced')
END;
END;
CREATE TRIGGER fkc_delete_on_languages_link
BEFORE INSERT ON books_languages_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
WHEN (SELECT id from languages WHERE id=NEW.lang_code) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: lang_code not in languages')
END;
END;
CREATE TRIGGER fkc_delete_on_publishers
BEFORE DELETE ON publishers
BEGIN
SELECT CASE
WHEN (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=OLD.id) > 0
THEN RAISE(ABORT, 'Foreign key violation: publishers is still referenced')
END;
END;
CREATE TRIGGER fkc_delete_on_series
BEFORE DELETE ON series
BEGIN
SELECT CASE
WHEN (SELECT COUNT(id) FROM books_series_link WHERE series=OLD.id) > 0
THEN RAISE(ABORT, 'Foreign key violation: series is still referenced')
END;
END;
CREATE TRIGGER fkc_delete_on_tags
BEFORE DELETE ON tags
BEGIN
SELECT CASE
WHEN (SELECT COUNT(id) FROM books_tags_link WHERE tag=OLD.id) > 0
THEN RAISE(ABORT, 'Foreign key violation: tags is still referenced')
END;
END;
CREATE TRIGGER fkc_insert_books_authors_link
BEFORE INSERT ON books_authors_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
WHEN (SELECT id from authors WHERE id=NEW.author) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: author not in authors')
END;
END;
CREATE TRIGGER fkc_insert_books_publishers_link
BEFORE INSERT ON books_publishers_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
WHEN (SELECT id from publishers WHERE id=NEW.publisher) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: publisher not in publishers')
END;
END;
CREATE TRIGGER fkc_insert_books_ratings_link
BEFORE INSERT ON books_ratings_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
WHEN (SELECT id from ratings WHERE id=NEW.rating) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: rating not in ratings')
END;
END;
CREATE TRIGGER fkc_insert_books_series_link
BEFORE INSERT ON books_series_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
WHEN (SELECT id from series WHERE id=NEW.series) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: series not in series')
END;
END;
CREATE TRIGGER fkc_insert_books_tags_link
BEFORE INSERT ON books_tags_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
WHEN (SELECT id from tags WHERE id=NEW.tag) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: tag not in tags')
END;
END;
CREATE TRIGGER fkc_update_books_authors_link_a
BEFORE UPDATE OF book ON books_authors_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_update_books_authors_link_b
BEFORE UPDATE OF author ON books_authors_link
BEGIN
SELECT CASE
WHEN (SELECT id from authors WHERE id=NEW.author) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: author not in authors')
END;
END;
CREATE TRIGGER fkc_update_books_languages_link_a
BEFORE UPDATE OF book ON books_languages_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_update_books_languages_link_b
BEFORE UPDATE OF lang_code ON books_languages_link
BEGIN
SELECT CASE
WHEN (SELECT id from languages WHERE id=NEW.lang_code) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: lang_code not in languages')
END;
END;
CREATE TRIGGER fkc_update_books_publishers_link_a
BEFORE UPDATE OF book ON books_publishers_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_update_books_publishers_link_b
BEFORE UPDATE OF publisher ON books_publishers_link
BEGIN
SELECT CASE
WHEN (SELECT id from publishers WHERE id=NEW.publisher) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: publisher not in publishers')
END;
END;
CREATE TRIGGER fkc_update_books_ratings_link_a
BEFORE UPDATE OF book ON books_ratings_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_update_books_ratings_link_b
BEFORE UPDATE OF rating ON books_ratings_link
BEGIN
SELECT CASE
WHEN (SELECT id from ratings WHERE id=NEW.rating) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: rating not in ratings')
END;
END;
CREATE TRIGGER fkc_update_books_series_link_a
BEFORE UPDATE OF book ON books_series_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_update_books_series_link_b
BEFORE UPDATE OF series ON books_series_link
BEGIN
SELECT CASE
WHEN (SELECT id from series WHERE id=NEW.series) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: series not in series')
END;
END;
CREATE TRIGGER fkc_update_books_tags_link_a
BEFORE UPDATE OF book ON books_tags_link
BEGIN
SELECT CASE
WHEN (SELECT id from books WHERE id=NEW.book) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: book not in books')
END;
END;
CREATE TRIGGER fkc_update_books_tags_link_b
BEFORE UPDATE OF tag ON books_tags_link
BEGIN
SELECT CASE
WHEN (SELECT id from tags WHERE id=NEW.tag) IS NULL
THEN RAISE(ABORT, 'Foreign key violation: tag not in tags')
END;
END;
CREATE TRIGGER series_insert_trg
AFTER INSERT ON series
BEGIN
UPDATE series SET sort=NEW.name WHERE id=NEW.id;
END;
CREATE TRIGGER series_update_trg
AFTER UPDATE ON series
BEGIN
UPDATE series SET sort=NEW.name WHERE id=NEW.id;
END;
pragma user_version=21;

View file

@ -3,7 +3,7 @@
* PHP EPub Meta library * PHP EPub Meta library
* *
* @author Andreas Gohr <andi@splitbrain.org> * @author Andreas Gohr <andi@splitbrain.org>
* @author Sébastien Lucas <sebastien@slucas.fr> * @author Sébastien Lucas <sebastien@slucas.fr>
*/ */
require_once(realpath( dirname( __FILE__ ) ) . '/tbszip.php'); require_once(realpath( dirname( __FILE__ ) ) . '/tbszip.php');
@ -26,12 +26,13 @@ class EPub {
* Constructor * Constructor
* *
* @param string $file path to epub file to work on * @param string $file path to epub file to work on
* @param string $zipClass class to handle zip
* @throws Exception if metadata could not be loaded * @throws Exception if metadata could not be loaded
*/ */
public function __construct($file){ public function __construct($file, $zipClass = 'clsTbsZip'){
// open file // open file
$this->file = $file; $this->file = $file;
$this->zip = new clsTbsZip(); $this->zip = new $zipClass();
if(!$this->zip->Open($this->file)){ if(!$this->zip->Open($this->file)){
throw new Exception('Failed to read epub file'); throw new Exception('Failed to read epub file');
} }
@ -302,6 +303,37 @@ class EPub {
return $this->getset('dc:description',$description); return $this->getset('dc:description',$description);
} }
/**
* Set or get the book's Unique Identifier
*
* @param string Unique identifier
*/
public function Uuid($uuid = false)
{
$nodes = $this->xpath->query('/opf:package');
if ($nodes->length !== 1) {
$error = sprintf('Cannot find ebook identifier');
throw new Exception($error);
}
$identifier = $nodes->item(0)->attr('unique-identifier');
$res = $this->getset('dc:identifier', $uuid, 'id', $identifier);
return $res;
}
/**
* Set or get the book's URI
*
* @param string URI
*/
public function Uri($uri = false)
{
$res = $this->getset('dc:identifier', $uri, 'opf:scheme', 'URI');
return $res;
}
/** /**
* Set or get the book's ISBN number * Set or get the book's ISBN number
* *