1 #include <qtextdocument.h> // Qt::escape is currently defined here...
4 #include "opshandler.h"
5 #include "xmlerrorhandler.h"
6 #include "extractzip.h"
8 #include "containerhandler.h"
9 #include "ncxhandler.h"
13 const int COVER_WIDTH = 53;
14 const int COVER_HEIGHT = 59;
16 static QImage makeCover(const QString &path)
18 return QImage(path).scaled(COVER_WIDTH, COVER_HEIGHT,
19 Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
20 scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
23 Book::Book(const QString &p, QObject *parent): QObject(parent), loaded(false)
28 mPath = info.absoluteFilePath();
29 title = info.baseName();
30 cover = makeCover(":/icons/book.png");
52 if (path().isEmpty()) {
56 if (!extract(QStringList())) {
74 if (path().isEmpty()) {
78 if (!extractMetaData()) {
93 QDir::setCurrent(QDir::rootPath());
97 QString Book::tmpDir() const
99 QString tmpName = QFileInfo(mTempFile.fileName()).fileName();
100 return QDir(QDir::temp().absoluteFilePath("dorian")).
101 absoluteFilePath(tmpName);
104 bool Book::extract(const QStringList &excludedExtensions)
108 QString tmp = tmpDir();
109 qDebug() << "Extracting" << mPath << "to" << tmp;
112 QDir::setCurrent(QDir::rootPath());
113 if (!clearDir(tmp)) {
114 qCritical() << "Book::extract: Failed to remove" << tmp;
119 if (!d.mkpath(tmp)) {
120 qCritical() << "Book::extract: Could not create" << tmp;
125 // If book comes from resource, copy it to the temporary directory first
126 QString bookPath = path();
127 if (bookPath.startsWith(":/books/")) {
129 QString dst(QDir(tmp).absoluteFilePath("book.epub"));
130 if (!src.copy(dst)) {
131 qCritical() << "Book::extract: Failed to copy built-in book"
132 << bookPath << "to" << dst;
138 QString oldDir = QDir::currentPath();
139 if (!QDir::setCurrent(tmp)) {
140 qCritical() << "Book::extract: Could not change to" << tmp;
143 ret = extractZip(bookPath, excludedExtensions);
145 qCritical() << "Book::extract: Extracting ZIP failed";
147 QDir::setCurrent(oldDir);
159 QString opsFileName = opsPath();
160 qDebug() << "Parsing OPS file" << opsFileName;
161 QFile opsFile(opsFileName);
162 QXmlSimpleReader reader;
163 QXmlInputSource *source = new QXmlInputSource(&opsFile);
164 OpsHandler *opsHandler = new OpsHandler(*this);
165 XmlErrorHandler *errorHandler = new XmlErrorHandler();
166 reader.setContentHandler(opsHandler);
167 reader.setErrorHandler(errorHandler);
168 ret = reader.parse(source);
173 // Initially, put all content items in the chapter list.
174 // This will be refined by parsing the NCX file later
179 QStringList coverKeys;
180 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
181 foreach (QString key, coverKeys) {
182 if (content.contains(key)) {
183 coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
187 if (coverPath.isEmpty()) {
189 QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
190 if (QFileInfo(coverJpeg).exists()) {
191 coverPath = coverJpeg;
194 if (!coverPath.isEmpty()) {
195 qDebug() << "Loading cover image from" << coverPath;
196 cover = makeCover(coverPath);
199 // If there is an "ncx" item in content, parse it: That's the real table of
202 if (content.contains("ncx")) {
203 ncxFileName = content["ncx"].href;
204 } else if (content.contains("ncxtoc")) {
205 ncxFileName = content["ncxtoc"].href;
206 } else if (content.contains("toc")) {
207 ncxFileName = content["toc"].href;
209 qDebug() << "No NCX table of contents";
211 if (!ncxFileName.isEmpty()) {
212 qDebug() << "Parsing NCX file" << ncxFileName;
213 QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
214 source = new QXmlInputSource(&ncxFile);
215 NcxHandler *ncxHandler = new NcxHandler(*this);
216 errorHandler = new XmlErrorHandler();
217 reader.setContentHandler(ncxHandler);
218 reader.setErrorHandler(errorHandler);
219 ret = reader.parse(source);
225 // Calculate book part sizes
227 foreach (QString part, parts) {
228 QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
229 content[part].size = info.size();
230 size += content[part].size;
236 bool Book::clearDir(const QString &dir)
242 QDirIterator i(dir, QDirIterator::Subdirectories);
243 while (i.hasNext()) {
244 QString entry = i.next();
245 if (entry.endsWith("/.") || entry.endsWith("/..")) {
248 QFileInfo info(entry);
250 if (!clearDir(entry)) {
255 if (!QFile::remove(entry)) {
256 qCritical() << "Book::clearDir: Could not remove" << entry;
257 // FIXME: To be investigated: This is happening too often
287 qDebug() << "path" << path();
289 QVariantHash data = BookDb::instance()->load(path());
290 title = data["title"].toString();
292 creators = data["creators"].toStringList();
293 date = data["date"].toString();
294 publisher = data["publisher"].toString();
295 datePublished = data["datepublished"].toString();
296 subject = data["subject"].toString();
297 source = data["source"].toString();
298 rights = data["rights"].toString();
299 mLastBookmark.part = data["lastpart"].toInt();
300 mLastBookmark.pos = data["lastpos"].toReal();
301 cover = data["cover"].value<QImage>().scaled(COVER_WIDTH,
302 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
303 if (cover.isNull()) {
304 cover = makeCover(":/icons/book.png");
306 int size = data["bookmarks"].toInt();
307 for (int i = 0; i < size; i++) {
308 int part = data[QString("bookmark%1part").arg(i)].toInt();
309 qreal pos = data[QString("bookmark%1pos").arg(i)].toReal();
310 QString note = data[QString("bookmark%1note").arg(i)].toString();
311 mBookmarks.append(Bookmark(part, pos, note));
321 data["title"] = title;
322 data["creators"] = creators;
324 data["publisher"] = publisher;
325 data["datepublished"] = datePublished;
326 data["subject"] = subject;
327 data["source"] = source;
328 data["rights"] = rights;
329 data["lastpart"] = mLastBookmark.part;
330 data["lastpos"] = mLastBookmark.pos;
331 data["cover"] = cover;
332 data["bookmarks"] = mBookmarks.size();
333 for (int i = 0; i < mBookmarks.size(); i++) {
334 data[QString("bookmark%1part").arg(i)] = mBookmarks[i].part;
335 data[QString("bookmark%1pos").arg(i)] = mBookmarks[i].pos;
336 data[QString("bookmark%1note").arg(i)] = mBookmarks[i].note;
338 BookDb::instance()->save(path(), data);
341 void Book::setLastBookmark(int part, qreal position)
345 mLastBookmark.part = part;
346 mLastBookmark.pos = position;
350 Book::Bookmark Book::lastBookmark()
353 return Book::Bookmark(mLastBookmark);
356 void Book::addBookmark(int part, qreal position, const QString ¬e)
359 mBookmarks.append(Bookmark(part, position, note));
360 qSort(mBookmarks.begin(), mBookmarks.end());
364 void Book::deleteBookmark(int index)
367 mBookmarks.removeAt(index);
371 QList<Book::Bookmark> Book::bookmarks()
377 QString Book::opsPath()
383 QFile container(tmpDir() + "/META-INF/container.xml");
384 qDebug() << container.fileName();
385 QXmlSimpleReader reader;
386 QXmlInputSource *source = new QXmlInputSource(&container);
387 ContainerHandler *containerHandler = new ContainerHandler();
388 XmlErrorHandler *errorHandler = new XmlErrorHandler();
389 reader.setContentHandler(containerHandler);
390 reader.setErrorHandler(errorHandler);
391 if (reader.parse(source)) {
392 ret = tmpDir() + "/" + containerHandler->rootFile;
393 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
394 qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
397 delete containerHandler;
402 QString Book::rootPath()
413 if (creators.length()) {
414 ret += "\nBy " + creators[0];
415 for (int i = 1; i < creators.length(); i++) {
416 ret += ", " + creators[i];
425 QString Book::shortName()
428 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
431 QImage Book::coverImage()
437 int Book::chapterFromPart(int index)
443 QString partId = parts[index];
444 QString partHref = content[partId].href;
446 for (int i = 0; i < chapters.size(); i++) {
447 QString id = chapters[i];
448 QString href = content[id].href;
449 QString baseRef(href);
450 QUrl url(QString("file://") + href);
451 if (url.hasFragment()) {
452 QString fragment = url.fragment();
453 baseRef.chop(fragment.length() + 1);
455 if (baseRef == partHref) {
457 // Don't break, keep looking
464 int Book::partFromChapter(int index, QString &fragment)
469 QString id = chapters[index];
470 QString href = content[id].href;
471 int hashPos = href.indexOf("#");
473 fragment = href.mid(hashPos);
474 href = href.left(hashPos);
477 qDebug() << "Chapter" << index;
478 qDebug() << " id" << id;
479 qDebug() << " href" << href;
480 qDebug() << " fragment" << fragment;
482 for (int i = 0; i < parts.size(); i++) {
483 QString partId = parts[i];
484 if (content[partId].href == href) {
485 qDebug() << "Part index for" << href << "is" << i;
490 qWarning() << "Book::partFromChapter: Could not find part index for"
495 qreal Book::getProgress(int part, qreal position)
498 Q_ASSERT(part < parts.size());
501 for (int i = 0; i < part; i++) {
503 partSize += content[key].size;
506 partSize += content[key].size * position;
507 return partSize / (qreal)size;
510 bool Book::extractMetaData()
512 QStringList excludedExtensions;
513 excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm" << ".gif"
514 << ".png" << ".css" << "*.ttf" << "mimetype";
515 return extract(excludedExtensions);
522 // Load book from old database (QSettings)
525 QString key = "book/" + path() + "/";
526 title = settings.value(key + "title").toString();
528 creators = settings.value(key + "creators").toStringList();
529 date = settings.value(key + "date").toString();
530 publisher = settings.value(key + "publisher").toString();
531 datePublished = settings.value(key + "datepublished").toString();
532 subject = settings.value(key + "subject").toString();
533 source = settings.value(key + "source").toString();
534 rights = settings.value(key + "rights").toString();
535 mLastBookmark.part = settings.value(key + "lastpart").toInt();
536 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
537 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
538 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
539 if (cover.isNull()) {
540 cover = makeCover(":/icons/book.png");
542 int size = settings.value(key + "bookmarks").toInt();
543 for (int i = 0; i < size; i++) {
544 int part = settings.value(key + "bookmark" + QString::number(i) +
546 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
548 qDebug() << QString("Bookmark %1 at part %2, %3").
549 arg(i).arg(part).arg(pos);
550 mBookmarks.append(Bookmark(part, pos));
553 // Save book to new database
562 BookDb::instance()->remove(path());