-#include <QDir>
-#include <QString>
-#include <QDebug>
-#include <QtXml>
#include <qtextdocument.h> // Qt::escape is currently defined here...
-#include <QDirIterator>
-#include <QFileInfo>
-#include <QtAlgorithms>
-#include <QCryptographicHash>
+#include <QtGui>
#include "book.h"
#include "opshandler.h"
#include "containerhandler.h"
#include "ncxhandler.h"
#include "trace.h"
+#include "bookdb.h"
const int COVER_WIDTH = 53;
const int COVER_HEIGHT = 59;
+const int COVER_MAX = 512 * 1024;
-static QImage makeCover(const QString &path)
-{
- return QImage(path).scaled(COVER_WIDTH, COVER_HEIGHT,
- Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
- scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
-}
-
-Book::Book(const QString &p, QObject *parent): QObject(parent)
+Book::Book(const QString &p, QObject *parent): QObject(parent), loaded(false)
{
mPath = "";
if (p.size()) {
QFileInfo info(p);
mPath = info.absoluteFilePath();
title = info.baseName();
- cover = makeCover(":/icons/book.png");
mTempFile.open();
}
}
-QString Book::path() const
+Book::~Book()
+{
+ close();
+}
+
+QString Book::path()
{
return mPath;
}
bool Book::open()
{
- Trace t("Book::open");
- t.trace(path());
+ TRACE;
+ qDebug() << path();
close();
clear();
+ load();
if (path().isEmpty()) {
title = "No book";
return false;
}
- if (!extract()) {
+ if (!extract(QStringList())) {
return false;
}
if (!parse()) {
return false;
}
+ dateOpened = QDateTime::currentDateTime().toUTC();
save();
emit opened(path());
return true;
void Book::peek()
{
- Trace t("Book::peek");
- t.trace(path());
+ TRACE;
+ qDebug() << path();
close();
clear();
+ load();
if (path().isEmpty()) {
title = "No book";
return;
QString Book::tmpDir() const
{
QString tmpName = QFileInfo(mTempFile.fileName()).fileName();
- return QDir::tempPath() + "/dorian/" + tmpName;
+ return QDir(QDir::temp().absoluteFilePath("dorian")).
+ absoluteFilePath(tmpName);
}
-bool Book::extract()
+bool Book::extract(const QStringList &excludedExtensions)
{
- Trace t("Book::extract");
+ TRACE;
bool ret = false;
QString tmp = tmpDir();
- t.trace("Extracting " + mPath + " to " + tmp);
+ qDebug() << "Extracting" << mPath << "to" << tmp;
+ load();
QDir::setCurrent(QDir::rootPath());
if (!clearDir(tmp)) {
qCritical() << "Book::extract: Failed to remove" << tmp;
return false;
}
- QDir d;
- if (!d.mkpath(tmp)) {
- qCritical() << "Book::extract: Could not create" << tmp;
- return false;
+ QDir d(tmp);
+ if (!d.exists()) {
+ if (!d.mkpath(tmp)) {
+ qCritical() << "Book::extract: Could not create" << tmp;
+ return false;
+ }
}
// If book comes from resource, copy it to the temporary directory first
QString bookPath = path();
if (bookPath.startsWith(":/books/")) {
QFile src(bookPath);
- QString dst(tmp + "/book.epub");
+ QString dst(QDir(tmp).absoluteFilePath("book.epub"));
if (!src.copy(dst)) {
- qCritical() << "Book::extract: Failed to copy built-in book to"
- << dst;
+ qCritical() << "Book::extract: Failed to copy built-in book"
+ << bookPath << "to" << dst;
return false;
}
bookPath = dst;
qCritical() << "Book::extract: Could not change to" << tmp;
return false;
}
- ret = extractZip(bookPath);
+ ret = extractZip(bookPath, excludedExtensions);
if (!ret) {
qCritical() << "Book::extract: Extracting ZIP failed";
}
bool Book::parse()
{
- Trace t("Book::parse");
+ TRACE;
+
+ load();
// Parse OPS file
bool ret = false;
QString opsFileName = opsPath();
- t.trace("Parsing OPS file" + opsFileName);
+ qDebug() << "Parsing OPS file" << opsFileName;
QFile opsFile(opsFileName);
QXmlSimpleReader reader;
QXmlInputSource *source = new QXmlInputSource(&opsFile);
chapters = parts;
// Load cover image
+ QString coverPath;
QStringList coverKeys;
coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
foreach (QString key, coverKeys) {
if (content.contains(key)) {
- t.trace("Loading cover image from " + content[key].href);
- cover = makeCover(content[key].href);
+ coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
break;
}
}
+ if (coverPath.isEmpty()) {
+ // Last resort
+ QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
+ if (QFileInfo(coverJpeg).exists()) {
+ coverPath = coverJpeg;
+ }
+ }
+ if (!coverPath.isEmpty()) {
+ qDebug() << "Loading cover image from" << coverPath;
+ cover = makeCover(coverPath);
+ }
- // If there is an "ncx" item in content, parse it: That's the real table of
- // contents
+ // If there is an "ncx" item in content, parse it: That's the real table
+ // of contents
+ QString ncxFileName;
if (content.contains("ncx")) {
- QString ncxFileName = content["ncx"].href;
- t.trace("Parsing NCX file " + ncxFileName);
- QFile ncxFile(ncxFileName);
+ ncxFileName = content["ncx"].href;
+ } else if (content.contains("ncxtoc")) {
+ ncxFileName = content["ncxtoc"].href;
+ } else if (content.contains("toc")) {
+ ncxFileName = content["toc"].href;
+ } else {
+ qDebug() << "No NCX table of contents";
+ }
+ if (!ncxFileName.isEmpty()) {
+ qDebug() << "Parsing NCX file" << ncxFileName;
+ QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
source = new QXmlInputSource(&ncxFile);
NcxHandler *ncxHandler = new NcxHandler(*this);
errorHandler = new XmlErrorHandler();
reader.setContentHandler(ncxHandler);
reader.setErrorHandler(errorHandler);
ret = reader.parse(source);
- delete ncxHandler;
delete errorHandler;
+ delete ncxHandler;
delete source;
}
// Calculate book part sizes
size = 0;
foreach (QString part, parts) {
- QFileInfo info(content[part].href);
+ QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
content[part].size = info.size();
size += content[part].size;
- t.trace(QString("Size of part %1: %2").arg(part).arg(content[part].size));
}
return ret;
void Book::load()
{
- Trace t("Book::load");
- t.trace("path: " + path());
- QSettings settings;
- QString key = "book/" + path() + "/";
- t.trace("key: " + key);
+ if (loaded) {
+ return;
+ }
- // Load book info
- title = settings.value(key + "title").toString();
- t.trace(title);
- creators = settings.value(key + "creators").toStringList();
- date = settings.value(key + "date").toString();
- publisher = settings.value(key + "publisher").toString();
- datePublished = settings.value(key + "datepublished").toString();
- subject = settings.value(key + "subject").toString();
- source = settings.value(key + "source").toString();
- rights = settings.value(key + "rights").toString();
- mLastBookmark.part = settings.value(key + "lastpart").toInt();
- mLastBookmark.pos = settings.value(key + "lastpos").toReal();
- cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
- COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ TRACE;
+ loaded = true;
+ qDebug() << "path" << path();
+
+ QVariantHash data = BookDb::instance()->load(path());
+ title = data["title"].toString();
+ qDebug() << title;
+ creators = data["creators"].toStringList();
+ date = data["date"].toString();
+ publisher = data["publisher"].toString();
+ datePublished = data["datepublished"].toString();
+ subject = data["subject"].toString();
+ source = data["source"].toString();
+ rights = data["rights"].toString();
+ mLastBookmark.part = data["lastpart"].toInt();
+ mLastBookmark.pos = data["lastpos"].toReal();
+ cover = data["cover"].value<QImage>();
if (cover.isNull()) {
cover = makeCover(":/icons/book.png");
}
-
- // Load bookmarks
- int size = settings.value(key + "bookmarks").toInt();
+ int size = data["bookmarks"].toInt();
for (int i = 0; i < size; i++) {
- int part = settings.value(key + "bookmark" + QString::number(i) +
- "/part").toInt();
- qreal pos = settings.value(key + "bookmark" + QString::number(i) +
- "/pos").toReal();
- t.trace(QString("Bookmark %1 at part %2, %3").
- arg(i).arg(part).arg(pos));
- mBookmarks.append(Bookmark(part, pos));
+ int part = data[QString("bookmark%1part").arg(i)].toInt();
+ qreal pos = data[QString("bookmark%1pos").arg(i)].toReal();
+ QString note = data[QString("bookmark%1note").arg(i)].toString();
+ mBookmarks.append(Bookmark(part, pos, note));
}
+ dateAdded = data["dateadded"].toDateTime();
+ dateOpened = data["dateopened"].toDateTime();
}
void Book::save()
{
- Trace t("Book::save");
- QSettings settings;
- QString key = "book/" + path() + "/";
- t.trace("key: " + key);
-
- // Save book info
- settings.setValue(key + "title", title);
- t.trace("title: " + title);
- settings.setValue(key + "creators", creators);
- settings.setValue(key + "date", date);
- settings.setValue(key + "publisher", publisher);
- settings.setValue(key + "datepublished", datePublished);
- settings.setValue(key + "subject", subject);
- settings.setValue(key + "source", source);
- settings.setValue(key + "rights", rights);
- settings.setValue(key + "lastpart", mLastBookmark.part);
- settings.setValue(key + "lastpos", mLastBookmark.pos);
- settings.setValue(key + "cover", cover);
-
- // Save bookmarks
- settings.setValue(key + "bookmarks", mBookmarks.size());
+ TRACE;
+
+ load();
+ QVariantHash data;
+ data["title"] = title;
+ data["creators"] = creators;
+ data["date"] = date;
+ data["publisher"] = publisher;
+ data["datepublished"] = datePublished;
+ data["subject"] = subject;
+ data["source"] = source;
+ data["rights"] = rights;
+ data["lastpart"] = mLastBookmark.part;
+ data["lastpos"] = mLastBookmark.pos;
+ data["cover"] = cover;
+ data["bookmarks"] = mBookmarks.size();
for (int i = 0; i < mBookmarks.size(); i++) {
- t.trace(QString("Bookmark %1 at %2, %3").
- arg(i).arg(mBookmarks[i].part).arg(mBookmarks[i].pos));
- settings.setValue(key + "bookmark" + QString::number(i) + "/part",
- mBookmarks[i].part);
- settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
- mBookmarks[i].pos);
+ data[QString("bookmark%1part").arg(i)] = mBookmarks[i].part;
+ data[QString("bookmark%1pos").arg(i)] = mBookmarks[i].pos;
+ data[QString("bookmark%1note").arg(i)] = mBookmarks[i].note;
}
+ data["dateadded"] = dateAdded;
+ data["dateopened"] = dateOpened;
+ BookDb::instance()->save(path(), data);
}
-void Book::setLastBookmark(int part, qreal position)
+void Book::setLastBookmark(int part, qreal position, bool fast)
{
+ TRACE;
+ qDebug() << "Part" << part << "position" << position << "fast?" << fast;
+ if (!fast) {
+ load();
+ }
mLastBookmark.part = part;
mLastBookmark.pos = position;
- save();
+ if (!fast) {
+ save();
+ }
}
-Book::Bookmark Book::lastBookmark() const
+Book::Bookmark Book::lastBookmark()
{
+ load();
return Book::Bookmark(mLastBookmark);
}
-void Book::addBookmark(int part, qreal position)
+void Book::addBookmark(int part, qreal position, const QString ¬e)
{
- mBookmarks.append(Bookmark(part, position));
+ load();
+ mBookmarks.append(Bookmark(part, position, note));
qSort(mBookmarks.begin(), mBookmarks.end());
save();
}
+void Book::setBookmarkNote(int index, const QString ¬e)
+{
+ load();
+ if (index >= 0 && index < mBookmarks.length()) {
+ mBookmarks[index].note = note;
+ }
+ save();
+
+}
+
void Book::deleteBookmark(int index)
{
+ load();
mBookmarks.removeAt(index);
save();
}
-QList<Book::Bookmark> Book::bookmarks() const
+QList<Book::Bookmark> Book::bookmarks()
{
+ load();
return mBookmarks;
}
QString Book::opsPath()
{
- Trace t("Book::opsPath");
+ TRACE;
+ load();
QString ret;
QFile container(tmpDir() + "/META-INF/container.xml");
- t.trace(container.fileName());
+ qDebug() << container.fileName();
QXmlSimpleReader reader;
QXmlInputSource *source = new QXmlInputSource(&container);
ContainerHandler *containerHandler = new ContainerHandler();
if (reader.parse(source)) {
ret = tmpDir() + "/" + containerHandler->rootFile;
mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
- t.trace("OSP path: " + ret);
- t.trace("Root dir: " + mRootPath);
+ qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
}
delete errorHandler;
delete containerHandler;
return ret;
}
-QString Book::rootPath() const
+QString Book::rootPath()
{
+ load();
return mRootPath;
}
-QString Book::name() const
+QString Book::name()
{
+ load();
if (title.size()) {
QString ret = title;
if (creators.length()) {
- ret += "\nBy " + creators[0];
- for (int i = 1; i < creators.length(); i++) {
- ret += ", " + creators[i];
- }
+ ret += "\nBy " + creators.join(", ");
}
return ret;
- } else {
- return path();
}
+ return path();
}
-QString Book::shortName() const
+QString Book::shortName()
{
+ load();
return (title.isEmpty())? QFileInfo(path()).baseName(): title;
}
+QImage Book::coverImage()
+{
+ load();
+ return cover;
+}
+
int Book::chapterFromPart(int index)
{
- // FIXME
- Q_UNUSED(index);
+ TRACE;
+ load();
int ret = -1;
+
+ QString partId = parts[index];
+ QString partHref = content[partId].href;
+
+ for (int i = 0; i < chapters.size(); i++) {
+ QString id = chapters[i];
+ QString href = content[id].href;
+ int hashPos = href.indexOf("#");
+ if (hashPos != -1) {
+ href = href.left(hashPos);
+ }
+ if (href == partHref) {
+ ret = i;
+ // Don't break, keep looking
+ }
+ }
+
+ qDebug() << "Part" << index << partId << partHref << ":" << ret;
return ret;
}
-int Book::partFromChapter(int index)
+int Book::partFromChapter(int index, QString &fragment)
{
- Trace t("Book::partFromChapter");
+ TRACE;
+ load();
+ fragment.clear();
QString id = chapters[index];
QString href = content[id].href;
- QString baseRef(href);
- QUrl url(QString("file://") + href);
- if (url.hasFragment()) {
- QString fragment = url.fragment();
- baseRef.chop(fragment.length() + 1);
- }
-
- // Swipe through all content items to find the one matching the chapter href
- // FIXME: Do we need to index content items by href, too?
- QString contentKey;
- bool found = false;
- foreach (contentKey, content.keys()) {
- if (contentKey == id) {
- continue;
- }
- if (content[contentKey].href == baseRef) {
- found = true;
- t.trace(QString("Key for %1 is %2").arg(baseRef).arg(contentKey));
- break;
- }
- }
- if (!found) {
- t.trace("Could not find key for " + baseRef);
- return -1;
+ int hashPos = href.indexOf("#");
+ if (hashPos != -1) {
+ fragment = href.mid(hashPos);
+ href = href.left(hashPos);
}
- int partIndex = parts.indexOf(contentKey);
- if (partIndex == -1) {
- qCritical()
- << "Book::partFromChapter: Could not find part index of chapter"
- << id;
+
+ qDebug() << "Chapter" << index;
+ qDebug() << " id" << id;
+ qDebug() << " href" << href;
+ qDebug() << " fragment" << fragment;
+
+ for (int i = 0; i < parts.size(); i++) {
+ QString partId = parts[i];
+ if (content[partId].href == href) {
+ qDebug() << "Part index for" << href << "is" << i;
+ return i;
+ }
}
- return partIndex;
+
+ qWarning() << "Book::partFromChapter: Could not find part index for"
+ << href;
+ return -1;
}
qreal Book::getProgress(int part, qreal position)
{
+ load();
Q_ASSERT(part < parts.size());
QString key;
qreal partSize = 0;
bool Book::extractMetaData()
{
- // FIXME
- return extract();
+ QStringList excludedExtensions;
+ excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm" << ".gif"
+ << ".css" << "*.ttf" << "mimetype";
+ return extract(excludedExtensions);
+}
+
+void Book::upgrade()
+{
+ TRACE;
+
+ // Load book from old database (QSettings)
+ QSettings settings;
+ QString key = "book/" + path() + "/";
+ title = settings.value(key + "title").toString();
+ qDebug() << title;
+ creators = settings.value(key + "creators").toStringList();
+ date = settings.value(key + "date").toString();
+ publisher = settings.value(key + "publisher").toString();
+ datePublished = settings.value(key + "datepublished").toString();
+ subject = settings.value(key + "subject").toString();
+ source = settings.value(key + "source").toString();
+ rights = settings.value(key + "rights").toString();
+ mLastBookmark.part = settings.value(key + "lastpart").toInt();
+ mLastBookmark.pos = settings.value(key + "lastpos").toReal();
+ cover = settings.value(key + "cover").value<QImage>();
+ if (cover.isNull()) {
+ cover = makeCover(":/icons/book.png");
+ } else {
+ cover = makeCover(QPixmap::fromImage(cover));
+ }
+ int size = settings.value(key + "bookmarks").toInt();
+ for (int i = 0; i < size; i++) {
+ int part = settings.value(key + "bookmark" + QString::number(i) +
+ "/part").toInt();
+ qreal pos = settings.value(key + "bookmark" + QString::number(i) +
+ "/pos").toReal();
+ qDebug() << QString("Bookmark %1 at part %2, %3").
+ arg(i).arg(part).arg(pos);
+ mBookmarks.append(Bookmark(part, pos));
+ }
+
+ // Remove QSettings
+ settings.remove("book/" + path());
+
+ // Save book to new database
+ save();
+}
+
+void Book::remove()
+{
+ TRACE;
+ close();
+ BookDb::instance()->remove(path());
+}
+
+QImage Book::makeCover(const QString &fileName)
+{
+ TRACE;
+ qDebug() << fileName;
+ QFileInfo info(fileName);
+ if (info.isReadable() && (info.size() < COVER_MAX)) {
+ return makeCover(QPixmap(fileName));
+ }
+ return makeCover(QPixmap(":/icons/book.png"));
}
+
+QImage Book::makeCover(const QPixmap &pixmap)
+{
+ TRACE;
+ QPixmap src = pixmap.scaled(COVER_WIDTH, COVER_HEIGHT,
+ Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmap transparent(COVER_WIDTH, COVER_HEIGHT);
+ transparent.fill(Qt::transparent);
+
+ QPainter p;
+ p.begin(&transparent);
+ p.setCompositionMode(QPainter::CompositionMode_Source);
+ p.drawPixmap((COVER_WIDTH - src.width()) / 2,
+ (COVER_HEIGHT - src.height()) / 2, src);
+ p.end();
+
+ return transparent.toImage();
+}
+
+