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;
16 const int COVER_MAX = 512 * 1024;
18 Book::Book(const QString &p, QObject *parent): QObject(parent), loaded(false)
23 mPath = info.absoluteFilePath();
24 title = info.baseName();
46 if (path().isEmpty()) {
50 if (!extract(QStringList())) {
68 if (path().isEmpty()) {
72 if (!extractMetaData()) {
87 QDir::setCurrent(QDir::rootPath());
91 QString Book::tmpDir() const
93 QString tmpName = QFileInfo(mTempFile.fileName()).fileName();
94 return QDir(QDir::temp().absoluteFilePath("dorian")).
95 absoluteFilePath(tmpName);
98 bool Book::extract(const QStringList &excludedExtensions)
102 QString tmp = tmpDir();
103 qDebug() << "Extracting" << mPath << "to" << tmp;
106 QDir::setCurrent(QDir::rootPath());
107 if (!clearDir(tmp)) {
108 qCritical() << "Book::extract: Failed to remove" << tmp;
113 if (!d.mkpath(tmp)) {
114 qCritical() << "Book::extract: Could not create" << tmp;
119 // If book comes from resource, copy it to the temporary directory first
120 QString bookPath = path();
121 if (bookPath.startsWith(":/books/")) {
123 QString dst(QDir(tmp).absoluteFilePath("book.epub"));
124 if (!src.copy(dst)) {
125 qCritical() << "Book::extract: Failed to copy built-in book"
126 << bookPath << "to" << dst;
132 QString oldDir = QDir::currentPath();
133 if (!QDir::setCurrent(tmp)) {
134 qCritical() << "Book::extract: Could not change to" << tmp;
137 ret = extractZip(bookPath, excludedExtensions);
139 qCritical() << "Book::extract: Extracting ZIP failed";
141 QDir::setCurrent(oldDir);
153 QString opsFileName = opsPath();
154 qDebug() << "Parsing OPS file" << opsFileName;
155 QFile opsFile(opsFileName);
156 QXmlSimpleReader reader;
157 QXmlInputSource *source = new QXmlInputSource(&opsFile);
158 OpsHandler *opsHandler = new OpsHandler(*this);
159 XmlErrorHandler *errorHandler = new XmlErrorHandler();
160 reader.setContentHandler(opsHandler);
161 reader.setErrorHandler(errorHandler);
162 ret = reader.parse(source);
167 // Initially, put all content items in the chapter list.
168 // This will be refined by parsing the NCX file later
173 QStringList coverKeys;
174 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
175 foreach (QString key, coverKeys) {
176 if (content.contains(key)) {
177 coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
181 if (coverPath.isEmpty()) {
183 QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
184 if (QFileInfo(coverJpeg).exists()) {
185 coverPath = coverJpeg;
188 if (!coverPath.isEmpty()) {
189 qDebug() << "Loading cover image from" << coverPath;
190 cover = makeCover(coverPath);
193 // If there is an "ncx" item in content, parse it: That's the real table of
196 if (content.contains("ncx")) {
197 ncxFileName = content["ncx"].href;
198 } else if (content.contains("ncxtoc")) {
199 ncxFileName = content["ncxtoc"].href;
200 } else if (content.contains("toc")) {
201 ncxFileName = content["toc"].href;
203 qDebug() << "No NCX table of contents";
205 if (!ncxFileName.isEmpty()) {
206 qDebug() << "Parsing NCX file" << ncxFileName;
207 QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
208 source = new QXmlInputSource(&ncxFile);
209 NcxHandler *ncxHandler = new NcxHandler(*this);
210 errorHandler = new XmlErrorHandler();
211 reader.setContentHandler(ncxHandler);
212 reader.setErrorHandler(errorHandler);
213 ret = reader.parse(source);
219 // Calculate book part sizes
221 foreach (QString part, parts) {
222 QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
223 content[part].size = info.size();
224 size += content[part].size;
230 bool Book::clearDir(const QString &dir)
236 QDirIterator i(dir, QDirIterator::Subdirectories);
237 while (i.hasNext()) {
238 QString entry = i.next();
239 if (entry.endsWith("/.") || entry.endsWith("/..")) {
242 QFileInfo info(entry);
244 if (!clearDir(entry)) {
249 if (!QFile::remove(entry)) {
250 qCritical() << "Book::clearDir: Could not remove" << entry;
251 // FIXME: To be investigated: This is happening too often
281 qDebug() << "path" << path();
283 QVariantHash data = BookDb::instance()->load(path());
284 title = data["title"].toString();
286 creators = data["creators"].toStringList();
287 date = data["date"].toString();
288 publisher = data["publisher"].toString();
289 datePublished = data["datepublished"].toString();
290 subject = data["subject"].toString();
291 source = data["source"].toString();
292 rights = data["rights"].toString();
293 mLastBookmark.part = data["lastpart"].toInt();
294 mLastBookmark.pos = data["lastpos"].toReal();
295 cover = data["cover"].value<QImage>();
296 if (cover.isNull()) {
297 cover = makeCover(":/icons/book.png");
299 int size = data["bookmarks"].toInt();
300 for (int i = 0; i < size; i++) {
301 int part = data[QString("bookmark%1part").arg(i)].toInt();
302 qreal pos = data[QString("bookmark%1pos").arg(i)].toReal();
303 QString note = data[QString("bookmark%1note").arg(i)].toString();
304 mBookmarks.append(Bookmark(part, pos, note));
314 data["title"] = title;
315 data["creators"] = creators;
317 data["publisher"] = publisher;
318 data["datepublished"] = datePublished;
319 data["subject"] = subject;
320 data["source"] = source;
321 data["rights"] = rights;
322 data["lastpart"] = mLastBookmark.part;
323 data["lastpos"] = mLastBookmark.pos;
324 data["cover"] = cover;
325 data["bookmarks"] = mBookmarks.size();
326 for (int i = 0; i < mBookmarks.size(); i++) {
327 data[QString("bookmark%1part").arg(i)] = mBookmarks[i].part;
328 data[QString("bookmark%1pos").arg(i)] = mBookmarks[i].pos;
329 data[QString("bookmark%1note").arg(i)] = mBookmarks[i].note;
331 BookDb::instance()->save(path(), data);
334 void Book::setLastBookmark(int part, qreal position)
338 mLastBookmark.part = part;
339 mLastBookmark.pos = position;
343 Book::Bookmark Book::lastBookmark()
346 return Book::Bookmark(mLastBookmark);
349 void Book::addBookmark(int part, qreal position, const QString ¬e)
352 mBookmarks.append(Bookmark(part, position, note));
353 qSort(mBookmarks.begin(), mBookmarks.end());
357 void Book::deleteBookmark(int index)
360 mBookmarks.removeAt(index);
364 QList<Book::Bookmark> Book::bookmarks()
370 QString Book::opsPath()
376 QFile container(tmpDir() + "/META-INF/container.xml");
377 qDebug() << container.fileName();
378 QXmlSimpleReader reader;
379 QXmlInputSource *source = new QXmlInputSource(&container);
380 ContainerHandler *containerHandler = new ContainerHandler();
381 XmlErrorHandler *errorHandler = new XmlErrorHandler();
382 reader.setContentHandler(containerHandler);
383 reader.setErrorHandler(errorHandler);
384 if (reader.parse(source)) {
385 ret = tmpDir() + "/" + containerHandler->rootFile;
386 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
387 qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
390 delete containerHandler;
395 QString Book::rootPath()
406 if (creators.length()) {
407 ret += "\nBy " + creators[0];
408 for (int i = 1; i < creators.length(); i++) {
409 ret += ", " + creators[i];
417 QString Book::shortName()
420 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
423 QImage Book::coverImage()
429 int Book::chapterFromPart(int index)
435 QString partId = parts[index];
436 QString partHref = content[partId].href;
438 for (int i = 0; i < chapters.size(); i++) {
439 QString id = chapters[i];
440 QString href = content[id].href;
441 QString baseRef(href);
442 QUrl url(QString("file://") + href);
443 if (url.hasFragment()) {
444 QString fragment = url.fragment();
445 baseRef.chop(fragment.length() + 1);
447 if (baseRef == partHref) {
449 // Don't break, keep looking
456 int Book::partFromChapter(int index, QString &fragment)
461 QString id = chapters[index];
462 QString href = content[id].href;
463 int hashPos = href.indexOf("#");
465 fragment = href.mid(hashPos);
466 href = href.left(hashPos);
469 qDebug() << "Chapter" << index;
470 qDebug() << " id" << id;
471 qDebug() << " href" << href;
472 qDebug() << " fragment" << fragment;
474 for (int i = 0; i < parts.size(); i++) {
475 QString partId = parts[i];
476 if (content[partId].href == href) {
477 qDebug() << "Part index for" << href << "is" << i;
482 qWarning() << "Book::partFromChapter: Could not find part index for"
487 qreal Book::getProgress(int part, qreal position)
490 Q_ASSERT(part < parts.size());
493 for (int i = 0; i < part; i++) {
495 partSize += content[key].size;
498 partSize += content[key].size * position;
499 return partSize / (qreal)size;
502 bool Book::extractMetaData()
504 QStringList excludedExtensions;
505 excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm" << ".gif"
506 << ".css" << "*.ttf" << "mimetype";
507 return extract(excludedExtensions);
514 // Load book from old database (QSettings)
516 QString key = "book/" + path() + "/";
517 title = settings.value(key + "title").toString();
519 creators = settings.value(key + "creators").toStringList();
520 date = settings.value(key + "date").toString();
521 publisher = settings.value(key + "publisher").toString();
522 datePublished = settings.value(key + "datepublished").toString();
523 subject = settings.value(key + "subject").toString();
524 source = settings.value(key + "source").toString();
525 rights = settings.value(key + "rights").toString();
526 mLastBookmark.part = settings.value(key + "lastpart").toInt();
527 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
528 cover = settings.value(key + "cover").value<QImage>();
529 if (cover.isNull()) {
530 cover = makeCover(":/icons/book.png");
532 cover = makeCover(QPixmap::fromImage(cover));
534 int size = settings.value(key + "bookmarks").toInt();
535 for (int i = 0; i < size; i++) {
536 int part = settings.value(key + "bookmark" + QString::number(i) +
538 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
540 qDebug() << QString("Bookmark %1 at part %2, %3").
541 arg(i).arg(part).arg(pos);
542 mBookmarks.append(Bookmark(part, pos));
546 settings.remove("book/" + path());
548 // Save book to new database
556 BookDb::instance()->remove(path());
559 QImage Book::makeCover(const QString &fileName)
562 qDebug() << fileName;
563 QFileInfo info(fileName);
564 if (info.isReadable() && (info.size() < COVER_MAX)) {
565 return makeCover(QPixmap(fileName));
567 return makeCover(QPixmap(":/icons/book.png"));
570 QImage Book::makeCover(const QPixmap &pixmap)
573 QPixmap src = pixmap.scaled(COVER_WIDTH, COVER_HEIGHT,
574 Qt::KeepAspectRatio, Qt::SmoothTransformation);
575 QPixmap transparent(COVER_WIDTH, COVER_HEIGHT);
576 transparent.fill(Qt::transparent);
579 p.begin(&transparent);
580 p.setCompositionMode(QPainter::CompositionMode_Source);
581 p.drawPixmap((COVER_WIDTH - src.width()) / 2,
582 (COVER_HEIGHT - src.height()) / 2, src);
585 return transparent.toImage();