diff --git a/resources/epub-loader/BaseExport.class.php b/resources/epub-loader/BaseExport.class.php
new file mode 100644
index 0000000..95915b4
--- /dev/null
+++ b/resources/epub-loader/BaseExport.class.php
@@ -0,0 +1,209 @@
+
+ */
+
+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;
+ }
+
+}
+
+?>
diff --git a/resources/epub-loader/BookExport.class.php b/resources/epub-loader/BookExport.class.php
new file mode 100644
index 0000000..20eb829
--- /dev/null
+++ b/resources/epub-loader/BookExport.class.php
@@ -0,0 +1,122 @@
+
+ */
+
+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++, 'Language');
+ $this->mExport->SetProperty($i++, 'Description');
+ $this->mExport->SetProperty($i++, 'Subjects');
+ $this->mExport->SetProperty($i++, 'Cover');
+ $this->mExport->SetProperty($i++, 'Serie');
+ $this->mExport->SetProperty($i++, 'SerieIndex');
+ $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++, $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->mSerie);
+ $this->mExport->SetProperty($i++, $inBookInfo->mSerieIndex);
+
+ $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();
+ }
+
+}
+
+?>
diff --git a/resources/epub-loader/BookInfos.class.php b/resources/epub-loader/BookInfos.class.php
new file mode 100644
index 0000000..a9076eb
--- /dev/null
+++ b/resources/epub-loader/BookInfos.class.php
@@ -0,0 +1,65 @@
+
+ */
+
+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 $mTitleSort = '';
+ public $mAuthors = null;
+ public $mLanguage = '';
+ public $mDescription = '';
+ public $mSubjects = null;
+ public $mCover = '';
+ public $mSerie = '';
+ public $mSerieIndex = '';
+
+ /**
+ * 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->mTitleSort = $this->mTitle; // Use title for now
+ $this->mAuthors = $epub->Authors();
+ $this->mLanguage = $epub->Language();
+ $this->mDescription = $epub->Description();
+ $this->mSubjects = $epub->Subjects();
+ $this->mCover = $epub->getCoverItem();
+ $this->mSerie = $epub->Serie();
+ $this->mSerieIndex = $epub->SerieIndex();
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/resources/epub-loader/CalibreDbLoader.class.php b/resources/epub-loader/CalibreDbLoader.class.php
new file mode 100644
index 0000000..a391233
--- /dev/null
+++ b/resources/epub-loader/CalibreDbLoader.class.php
@@ -0,0 +1,254 @@
+
+ */
+
+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, '
');
+ }
+ 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->mTitleSort);
+ $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 $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', $author);
+ $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;
+ }
+ }
+
+}
+
+?>
diff --git a/resources/epub-loader/CsvExport.class.php b/resources/epub-loader/CsvExport.class.php
new file mode 100644
index 0000000..20b58c4
--- /dev/null
+++ b/resources/epub-loader/CsvExport.class.php
@@ -0,0 +1,69 @@
+
+ */
+
+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('', '
', '');
+
+ // 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) {
+ $text .= $value1 . self::CsvSeparator;
+ }
+ continue;
+ }
+ else {
+ $info = $value;
+ }
+ $text .= $info . self::CsvSeparator;
+ }
+
+ $this->mLines[] = $text;
+
+ $this->ClearProperties();
+ }
+
+ protected function GetContent()
+ {
+ $text = implode("\n", $this->mLines) . "\n";
+
+ return $text;
+ }
+
+}
+
+?>
diff --git a/resources/epub-loader/ZipFile.class.php b/resources/epub-loader/ZipFile.class.php
new file mode 100644
index 0000000..abcbd82
--- /dev/null
+++ b/resources/epub-loader/ZipFile.class.php
@@ -0,0 +1,119 @@
+
+ */
+
+/**
+ * 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);
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/resources/epub-loader/metadata_sqlite.sql b/resources/epub-loader/metadata_sqlite.sql
new file mode 100644
index 0000000..83f55c2
--- /dev/null
+++ b/resources/epub-loader/metadata_sqlite.sql
@@ -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;