5 #include <qtextdocument.h> // Qt::escape is currently defined here...
6 #include <QDirIterator>
8 #include <QtAlgorithms>
9 #include <QVariantHash>
12 #include "opshandler.h"
13 #include "xmlerrorhandler.h"
14 #include "extractzip.h"
16 #include "containerhandler.h"
17 #include "ncxhandler.h"
21 const int COVER_WIDTH = 53;
22 const int COVER_HEIGHT = 59;
24 static QImage makeCover(const QString &path)
26 return QImage(path).scaled(COVER_WIDTH, COVER_HEIGHT,
27 Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
28 scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
31 Book::Book(const QString &p, QObject *parent): QObject(parent)
36 mPath = info.absoluteFilePath();
37 title = info.baseName();
38 cover = makeCover(":/icons/book.png");
43 QString Book::path() const
50 Trace t("Book::open");
54 if (path().isEmpty()) {
58 if (!extract(QStringList())) {
71 Trace t("Book::peek");
75 if (path().isEmpty()) {
79 if (!extractMetaData()) {
91 Trace t("Book::close");
94 QDir::setCurrent(QDir::rootPath());
98 QString Book::tmpDir() const
100 QString tmpName = QFileInfo(mTempFile.fileName()).fileName();
101 return QDir(QDir::temp().absoluteFilePath("dorian")).
102 absoluteFilePath(tmpName);
105 bool Book::extract(const QStringList &excludedExtensions)
107 Trace t("Book::extract");
109 QString tmp = tmpDir();
110 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);
153 Trace t("Book::parse");
157 QString opsFileName = opsPath();
158 qDebug() << "Parsing OPS file" << opsFileName;
159 QFile opsFile(opsFileName);
160 QXmlSimpleReader reader;
161 QXmlInputSource *source = new QXmlInputSource(&opsFile);
162 OpsHandler *opsHandler = new OpsHandler(*this);
163 XmlErrorHandler *errorHandler = new XmlErrorHandler();
164 reader.setContentHandler(opsHandler);
165 reader.setErrorHandler(errorHandler);
166 ret = reader.parse(source);
171 // Initially, put all content items in the chapter list.
172 // This will be refined by parsing the NCX file later
177 QStringList coverKeys;
178 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
179 foreach (QString key, coverKeys) {
180 if (content.contains(key)) {
181 coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
185 if (coverPath.isEmpty()) {
187 QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
188 if (QFileInfo(coverJpeg).exists()) {
189 coverPath = coverJpeg;
192 if (!coverPath.isEmpty()) {
193 qDebug() << "Loading cover image from" << coverPath;
194 cover = makeCover(coverPath);
197 // If there is an "ncx" item in content, parse it: That's the real table of
200 if (content.contains("ncx")) {
201 ncxFileName = content["ncx"].href;
202 } else if (content.contains("ncxtoc")) {
203 ncxFileName = content["ncxtoc"].href;
204 } else if (content.contains("toc")) {
205 ncxFileName = content["toc"].href;
207 qDebug() << "No NCX table of contents";
209 if (!ncxFileName.isEmpty()) {
210 qDebug() << "Parsing NCX file" << ncxFileName;
211 QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
212 source = new QXmlInputSource(&ncxFile);
213 NcxHandler *ncxHandler = new NcxHandler(*this);
214 errorHandler = new XmlErrorHandler();
215 reader.setContentHandler(ncxHandler);
216 reader.setErrorHandler(errorHandler);
217 ret = reader.parse(source);
223 // Calculate book part sizes
225 foreach (QString part, parts) {
226 QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
227 content[part].size = info.size();
228 size += content[part].size;
234 bool Book::clearDir(const QString &dir)
240 QDirIterator i(dir, QDirIterator::Subdirectories);
241 while (i.hasNext()) {
242 QString entry = i.next();
243 if (entry.endsWith("/.") || entry.endsWith("/..")) {
246 QFileInfo info(entry);
248 if (!clearDir(entry)) {
253 if (!QFile::remove(entry)) {
254 qCritical() << "Book::clearDir: Could not remove" << entry;
255 // FIXME: To be investigated: This is happening too often
279 Trace t("Book::load");
280 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>().scaled(COVER_WIDTH,
296 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
297 if (cover.isNull()) {
298 cover = makeCover(":/icons/book.png");
300 int size = data["bookmarks"].toInt();
301 for (int i = 0; i < size; i++) {
302 int part = data[QString("bookmark%1part").arg(i)].toInt();
303 qreal pos = data[QString("bookmark%1pos").arg(i)].toReal();
304 mBookmarks.append(Bookmark(part, pos));
308 QString key = "book/" + path() + "/";
309 qDebug() << "key" << key;
312 title = settings.value(key + "title").toString();
314 creators = settings.value(key + "creators").toStringList();
315 date = settings.value(key + "date").toString();
316 publisher = settings.value(key + "publisher").toString();
317 datePublished = settings.value(key + "datepublished").toString();
318 subject = settings.value(key + "subject").toString();
319 source = settings.value(key + "source").toString();
320 rights = settings.value(key + "rights").toString();
321 mLastBookmark.part = settings.value(key + "lastpart").toInt();
322 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
323 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
324 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
325 if (cover.isNull()) {
326 cover = makeCover(":/icons/book.png");
330 int size = settings.value(key + "bookmarks").toInt();
331 for (int i = 0; i < size; i++) {
332 int part = settings.value(key + "bookmark" + QString::number(i) +
334 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
336 qDebug() << QString("Bookmark %1 at part %2, %3").
337 arg(i).arg(part).arg(pos);
338 mBookmarks.append(Bookmark(part, pos));
345 Trace t("Book::save");
349 data["title"] = title;
350 data["creators"] = creators;
352 data["publisher"] = publisher;
353 data["datepublished"] = datePublished;
354 data["subject"] = subject;
355 data["source"] = source;
356 data["rights"] = rights;
357 data["lastpart"] = mLastBookmark.part;
358 data["lastpos"] = mLastBookmark.pos;
359 data["cover"] = cover;
360 data["bookmarks"] = mBookmarks.size();
361 for (int i = 0; i < mBookmarks.size(); i++) {
362 data[QString("bookmark%1part").arg(i)] = mBookmarks[i].part;
363 data[QString("bookmark%1pos").arg(i)] = mBookmarks[i].pos;
365 BookDb::instance()->save(path(), data);
368 QString key = "book/" + path() + "/";
369 qDebug() << "key" << key;
372 settings.setValue(key + "title", title);
373 qDebug() << "title" << title;
374 settings.setValue(key + "creators", creators);
375 settings.setValue(key + "date", date);
376 settings.setValue(key + "publisher", publisher);
377 settings.setValue(key + "datepublished", datePublished);
378 settings.setValue(key + "subject", subject);
379 settings.setValue(key + "source", source);
380 settings.setValue(key + "rights", rights);
381 settings.setValue(key + "lastpart", mLastBookmark.part);
382 settings.setValue(key + "lastpos", mLastBookmark.pos);
383 settings.setValue(key + "cover", cover);
386 settings.setValue(key + "bookmarks", mBookmarks.size());
387 for (int i = 0; i < mBookmarks.size(); i++) {
388 qDebug() << QString("Bookmark %1 at %2, %3").
389 arg(i).arg(mBookmarks[i].part).arg(mBookmarks[i].pos);
390 settings.setValue(key + "bookmark" + QString::number(i) + "/part",
392 settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
398 void Book::setLastBookmark(int part, qreal position)
400 mLastBookmark.part = part;
401 mLastBookmark.pos = position;
405 Book::Bookmark Book::lastBookmark() const
407 return Book::Bookmark(mLastBookmark);
410 void Book::addBookmark(int part, qreal position)
412 mBookmarks.append(Bookmark(part, position));
413 qSort(mBookmarks.begin(), mBookmarks.end());
417 void Book::deleteBookmark(int index)
419 mBookmarks.removeAt(index);
423 QList<Book::Bookmark> Book::bookmarks() const
428 QString Book::opsPath()
430 Trace t("Book::opsPath");
433 QFile container(tmpDir() + "/META-INF/container.xml");
434 qDebug() << container.fileName();
435 QXmlSimpleReader reader;
436 QXmlInputSource *source = new QXmlInputSource(&container);
437 ContainerHandler *containerHandler = new ContainerHandler();
438 XmlErrorHandler *errorHandler = new XmlErrorHandler();
439 reader.setContentHandler(containerHandler);
440 reader.setErrorHandler(errorHandler);
441 if (reader.parse(source)) {
442 ret = tmpDir() + "/" + containerHandler->rootFile;
443 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
444 qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
447 delete containerHandler;
452 QString Book::rootPath() const
457 QString Book::name() const
461 if (creators.length()) {
462 ret += "\nBy " + creators[0];
463 for (int i = 1; i < creators.length(); i++) {
464 ret += ", " + creators[i];
473 QString Book::shortName() const
475 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
478 int Book::chapterFromPart(int index)
482 QString partId = parts[index];
483 QString partHref = content[partId].href;
485 for (int i = 0; i < chapters.size(); i++) {
486 QString id = chapters[i];
487 QString href = content[id].href;
488 QString baseRef(href);
489 QUrl url(QString("file://") + href);
490 if (url.hasFragment()) {
491 QString fragment = url.fragment();
492 baseRef.chop(fragment.length() + 1);
494 if (baseRef == partHref) {
496 // Don't break, keep looking
503 int Book::partFromChapter(int index)
505 Trace t("Book::partFromChapter");
506 QString id = chapters[index];
507 QString href = content[id].href;
508 int hashPos = href.indexOf("#");
510 href = href.left(hashPos);
513 qDebug() << "Chapter" << index;
514 qDebug() << " id" << id;
515 qDebug() << " href" << href;
517 for (int i = 0; i < parts.size(); i++) {
518 QString partId = parts[i];
519 if (content[partId].href == href) {
520 qDebug() << "Part index for" << href << "is" << i;
525 qWarning() << "Book::partFromChapter: Could not find part index for"
530 qreal Book::getProgress(int part, qreal position)
532 Q_ASSERT(part < parts.size());
535 for (int i = 0; i < part; i++) {
537 partSize += content[key].size;
540 partSize += content[key].size * position;
541 return partSize / (qreal)size;
544 bool Book::extractMetaData()
546 QStringList excludedExtensions;
547 excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm";
548 return extract(excludedExtensions);
553 Trace t("Book::upgrade");