1 #include <qtextdocument.h> // Qt::escape is currently defined here...
5 #include "opshandler.h"
6 #include "xmlerrorhandler.h"
7 #include "extractzip.h"
9 #include "containerhandler.h"
10 #include "ncxhandler.h"
14 const int COVER_WIDTH = 53;
15 const int COVER_HEIGHT = 59;
17 static QImage makeCover(const QString &path)
19 QPixmap src = QPixmap(path).scaled(COVER_WIDTH, COVER_HEIGHT,
20 Qt::KeepAspectRatio, Qt::SmoothTransformation);
21 QPixmap transparent(src.size());
22 transparent.fill(Qt::transparent);
25 p.begin(&transparent);
26 p.setCompositionMode(QPainter::CompositionMode_Source);
27 p.drawPixmap((COVER_WIDTH - src.width()) / 2,
28 (COVER_HEIGHT - src.height()) / 2, src);
29 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
32 return transparent.toImage();
35 Book::Book(const QString &p, QObject *parent): QObject(parent), loaded(false)
40 mPath = info.absoluteFilePath();
41 title = info.baseName();
42 cover = makeCover(":/icons/book.png");
64 if (path().isEmpty()) {
68 if (!extract(QStringList())) {
86 if (path().isEmpty()) {
90 if (!extractMetaData()) {
105 QDir::setCurrent(QDir::rootPath());
109 QString Book::tmpDir() const
111 QString tmpName = QFileInfo(mTempFile.fileName()).fileName();
112 return QDir(QDir::temp().absoluteFilePath("dorian")).
113 absoluteFilePath(tmpName);
116 bool Book::extract(const QStringList &excludedExtensions)
120 QString tmp = tmpDir();
121 qDebug() << "Extracting" << mPath << "to" << tmp;
124 QDir::setCurrent(QDir::rootPath());
125 if (!clearDir(tmp)) {
126 qCritical() << "Book::extract: Failed to remove" << tmp;
131 if (!d.mkpath(tmp)) {
132 qCritical() << "Book::extract: Could not create" << tmp;
137 // If book comes from resource, copy it to the temporary directory first
138 QString bookPath = path();
139 if (bookPath.startsWith(":/books/")) {
141 QString dst(QDir(tmp).absoluteFilePath("book.epub"));
142 if (!src.copy(dst)) {
143 qCritical() << "Book::extract: Failed to copy built-in book"
144 << bookPath << "to" << dst;
150 QString oldDir = QDir::currentPath();
151 if (!QDir::setCurrent(tmp)) {
152 qCritical() << "Book::extract: Could not change to" << tmp;
155 ret = extractZip(bookPath, excludedExtensions);
157 qCritical() << "Book::extract: Extracting ZIP failed";
159 QDir::setCurrent(oldDir);
171 QString opsFileName = opsPath();
172 qDebug() << "Parsing OPS file" << opsFileName;
173 QFile opsFile(opsFileName);
174 QXmlSimpleReader reader;
175 QXmlInputSource *source = new QXmlInputSource(&opsFile);
176 OpsHandler *opsHandler = new OpsHandler(*this);
177 XmlErrorHandler *errorHandler = new XmlErrorHandler();
178 reader.setContentHandler(opsHandler);
179 reader.setErrorHandler(errorHandler);
180 ret = reader.parse(source);
185 // Initially, put all content items in the chapter list.
186 // This will be refined by parsing the NCX file later
191 QStringList coverKeys;
192 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
193 foreach (QString key, coverKeys) {
194 if (content.contains(key)) {
195 coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
199 if (coverPath.isEmpty()) {
201 QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
202 if (QFileInfo(coverJpeg).exists()) {
203 coverPath = coverJpeg;
206 if (!coverPath.isEmpty()) {
207 qDebug() << "Loading cover image from" << coverPath;
208 cover = makeCover(coverPath);
211 // If there is an "ncx" item in content, parse it: That's the real table of
214 if (content.contains("ncx")) {
215 ncxFileName = content["ncx"].href;
216 } else if (content.contains("ncxtoc")) {
217 ncxFileName = content["ncxtoc"].href;
218 } else if (content.contains("toc")) {
219 ncxFileName = content["toc"].href;
221 qDebug() << "No NCX table of contents";
223 if (!ncxFileName.isEmpty()) {
224 qDebug() << "Parsing NCX file" << ncxFileName;
225 QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
226 source = new QXmlInputSource(&ncxFile);
227 NcxHandler *ncxHandler = new NcxHandler(*this);
228 errorHandler = new XmlErrorHandler();
229 reader.setContentHandler(ncxHandler);
230 reader.setErrorHandler(errorHandler);
231 ret = reader.parse(source);
237 // Calculate book part sizes
239 foreach (QString part, parts) {
240 QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
241 content[part].size = info.size();
242 size += content[part].size;
248 bool Book::clearDir(const QString &dir)
254 QDirIterator i(dir, QDirIterator::Subdirectories);
255 while (i.hasNext()) {
256 QString entry = i.next();
257 if (entry.endsWith("/.") || entry.endsWith("/..")) {
260 QFileInfo info(entry);
262 if (!clearDir(entry)) {
267 if (!QFile::remove(entry)) {
268 qCritical() << "Book::clearDir: Could not remove" << entry;
269 // FIXME: To be investigated: This is happening too often
299 qDebug() << "path" << path();
301 QVariantHash data = BookDb::instance()->load(path());
302 title = data["title"].toString();
304 creators = data["creators"].toStringList();
305 date = data["date"].toString();
306 publisher = data["publisher"].toString();
307 datePublished = data["datepublished"].toString();
308 subject = data["subject"].toString();
309 source = data["source"].toString();
310 rights = data["rights"].toString();
311 mLastBookmark.part = data["lastpart"].toInt();
312 mLastBookmark.pos = data["lastpos"].toReal();
313 cover = data["cover"].value<QImage>().scaled(COVER_WIDTH,
314 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
315 if (cover.isNull()) {
316 cover = makeCover(":/icons/book.png");
318 int size = data["bookmarks"].toInt();
319 for (int i = 0; i < size; i++) {
320 int part = data[QString("bookmark%1part").arg(i)].toInt();
321 qreal pos = data[QString("bookmark%1pos").arg(i)].toReal();
322 QString note = data[QString("bookmark%1note").arg(i)].toString();
323 mBookmarks.append(Bookmark(part, pos, note));
333 data["title"] = title;
334 data["creators"] = creators;
336 data["publisher"] = publisher;
337 data["datepublished"] = datePublished;
338 data["subject"] = subject;
339 data["source"] = source;
340 data["rights"] = rights;
341 data["lastpart"] = mLastBookmark.part;
342 data["lastpos"] = mLastBookmark.pos;
343 data["cover"] = cover;
344 data["bookmarks"] = mBookmarks.size();
345 for (int i = 0; i < mBookmarks.size(); i++) {
346 data[QString("bookmark%1part").arg(i)] = mBookmarks[i].part;
347 data[QString("bookmark%1pos").arg(i)] = mBookmarks[i].pos;
348 data[QString("bookmark%1note").arg(i)] = mBookmarks[i].note;
350 BookDb::instance()->save(path(), data);
353 void Book::setLastBookmark(int part, qreal position)
357 mLastBookmark.part = part;
358 mLastBookmark.pos = position;
362 Book::Bookmark Book::lastBookmark()
365 return Book::Bookmark(mLastBookmark);
368 void Book::addBookmark(int part, qreal position, const QString ¬e)
371 mBookmarks.append(Bookmark(part, position, note));
372 qSort(mBookmarks.begin(), mBookmarks.end());
376 void Book::deleteBookmark(int index)
379 mBookmarks.removeAt(index);
383 QList<Book::Bookmark> Book::bookmarks()
389 QString Book::opsPath()
395 QFile container(tmpDir() + "/META-INF/container.xml");
396 qDebug() << container.fileName();
397 QXmlSimpleReader reader;
398 QXmlInputSource *source = new QXmlInputSource(&container);
399 ContainerHandler *containerHandler = new ContainerHandler();
400 XmlErrorHandler *errorHandler = new XmlErrorHandler();
401 reader.setContentHandler(containerHandler);
402 reader.setErrorHandler(errorHandler);
403 if (reader.parse(source)) {
404 ret = tmpDir() + "/" + containerHandler->rootFile;
405 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
406 qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
409 delete containerHandler;
414 QString Book::rootPath()
425 if (creators.length()) {
426 ret += "\nBy " + creators[0];
427 for (int i = 1; i < creators.length(); i++) {
428 ret += ", " + creators[i];
437 QString Book::shortName()
440 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
443 QImage Book::coverImage()
449 int Book::chapterFromPart(int index)
455 QString partId = parts[index];
456 QString partHref = content[partId].href;
458 for (int i = 0; i < chapters.size(); i++) {
459 QString id = chapters[i];
460 QString href = content[id].href;
461 QString baseRef(href);
462 QUrl url(QString("file://") + href);
463 if (url.hasFragment()) {
464 QString fragment = url.fragment();
465 baseRef.chop(fragment.length() + 1);
467 if (baseRef == partHref) {
469 // Don't break, keep looking
476 int Book::partFromChapter(int index, QString &fragment)
481 QString id = chapters[index];
482 QString href = content[id].href;
483 int hashPos = href.indexOf("#");
485 fragment = href.mid(hashPos);
486 href = href.left(hashPos);
489 qDebug() << "Chapter" << index;
490 qDebug() << " id" << id;
491 qDebug() << " href" << href;
492 qDebug() << " fragment" << fragment;
494 for (int i = 0; i < parts.size(); i++) {
495 QString partId = parts[i];
496 if (content[partId].href == href) {
497 qDebug() << "Part index for" << href << "is" << i;
502 qWarning() << "Book::partFromChapter: Could not find part index for"
507 qreal Book::getProgress(int part, qreal position)
510 Q_ASSERT(part < parts.size());
513 for (int i = 0; i < part; i++) {
515 partSize += content[key].size;
518 partSize += content[key].size * position;
519 return partSize / (qreal)size;
522 bool Book::extractMetaData()
524 QStringList excludedExtensions;
525 excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm" << ".gif"
526 << ".css" << "*.ttf" << "mimetype";
527 return extract(excludedExtensions);
534 // Load book from old database (QSettings)
537 QString key = "book/" + path() + "/";
538 title = settings.value(key + "title").toString();
540 creators = settings.value(key + "creators").toStringList();
541 date = settings.value(key + "date").toString();
542 publisher = settings.value(key + "publisher").toString();
543 datePublished = settings.value(key + "datepublished").toString();
544 subject = settings.value(key + "subject").toString();
545 source = settings.value(key + "source").toString();
546 rights = settings.value(key + "rights").toString();
547 mLastBookmark.part = settings.value(key + "lastpart").toInt();
548 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
549 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
550 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
551 if (cover.isNull()) {
552 cover = makeCover(":/icons/book.png");
554 int size = settings.value(key + "bookmarks").toInt();
555 for (int i = 0; i < size; i++) {
556 int part = settings.value(key + "bookmark" + QString::number(i) +
558 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
560 qDebug() << QString("Bookmark %1 at part %2, %3").
561 arg(i).arg(part).arg(pos);
562 mBookmarks.append(Bookmark(part, pos));
565 // Save book to new database
574 BookDb::instance()->remove(path());