X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=model%2Fbook.cpp;h=d2098165497f2bc8183792b2dc67794f41fd6022;hb=b855b46f739479ca0d6dd15b73a4c56a8d8a2d2c;hp=66e239228f2a7ec966e59a3c8adfa0049de72f81;hpb=6d93a09221b5d3a237c5ba661e7240a9d5013837;p=dorian diff --git a/model/book.cpp b/model/book.cpp index 66e2392..d209816 100644 --- a/model/book.cpp +++ b/model/book.cpp @@ -1,11 +1,5 @@ -#include -#include -#include -#include #include // Qt::escape is currently defined here... -#include -#include -#include +#include #include "book.h" #include "opshandler.h" @@ -15,44 +9,45 @@ #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()) { @@ -63,9 +58,30 @@ bool Book::open() return true; } +void Book::peek() +{ + TRACE; + qDebug() << path(); + close(); + clear(); + load(); + if (path().isEmpty()) { + title = "No book"; + return; + } + if (!extractMetaData()) { + return; + } + if (!parse()) { + return; + } + save(); + close(); +} + void Book::close() { - Trace t("Book::close"); + TRACE; content.clear(); parts.clear(); QDir::setCurrent(QDir::rootPath()); @@ -74,35 +90,40 @@ void Book::close() QString Book::tmpDir() const { - return QDir::tempPath() + "/dorian/book"; + QString tmpName = QFileInfo(mTempFile.fileName()).fileName(); + 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; @@ -113,7 +134,7 @@ bool Book::extract() 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"; } @@ -123,12 +144,14 @@ bool Book::extract() 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); @@ -146,40 +169,59 @@ bool Book::parse() 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 + 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; @@ -230,113 +272,109 @@ void Book::clear() 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().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(); 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)); } } 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; } + BookDb::instance()->save(path(), data); } void Book::setLastBookmark(int part, qreal position) { + TRACE; + load(); mLastBookmark.part = part; mLastBookmark.pos = position; 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::deleteBookmark(int index) { + load(); mBookmarks.removeAt(index); save(); } -QList Book::bookmarks() const +QList 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(); @@ -346,8 +384,7 @@ QString Book::opsPath() 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; @@ -355,81 +392,97 @@ QString Book::opsPath() 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; @@ -441,3 +494,91 @@ qreal Book::getProgress(int part, qreal position) partSize += content[key].size * position; return partSize / (qreal)size; } + +bool Book::extractMetaData() +{ + 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(); + 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(); +} + +