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)
28 mPath = info.absoluteFilePath();
29 title = info.baseName();
30 cover = makeCover(":/icons/book.png");
35 QString Book::path() const
46 if (path().isEmpty()) {
50 if (!extract(QStringList())) {
67 if (path().isEmpty()) {
71 if (!extractMetaData()) {
86 QDir::setCurrent(QDir::rootPath());
90 QString Book::tmpDir() const
92 QString tmpName = QFileInfo(mTempFile.fileName()).fileName();
93 return QDir(QDir::temp().absoluteFilePath("dorian")).
94 absoluteFilePath(tmpName);
97 bool Book::extract(const QStringList &excludedExtensions)
101 QString tmp = tmpDir();
102 qDebug() << "Extracting" << mPath << "to" << tmp;
104 QDir::setCurrent(QDir::rootPath());
105 if (!clearDir(tmp)) {
106 qCritical() << "Book::extract: Failed to remove" << tmp;
111 if (!d.mkpath(tmp)) {
112 qCritical() << "Book::extract: Could not create" << tmp;
117 // If book comes from resource, copy it to the temporary directory first
118 QString bookPath = path();
119 if (bookPath.startsWith(":/books/")) {
121 QString dst(QDir(tmp).absoluteFilePath("book.epub"));
122 if (!src.copy(dst)) {
123 qCritical() << "Book::extract: Failed to copy built-in book"
124 << bookPath << "to" << dst;
130 QString oldDir = QDir::currentPath();
131 if (!QDir::setCurrent(tmp)) {
132 qCritical() << "Book::extract: Could not change to" << tmp;
135 ret = extractZip(bookPath, excludedExtensions);
137 qCritical() << "Book::extract: Extracting ZIP failed";
139 QDir::setCurrent(oldDir);
149 QString opsFileName = opsPath();
150 qDebug() << "Parsing OPS file" << opsFileName;
151 QFile opsFile(opsFileName);
152 QXmlSimpleReader reader;
153 QXmlInputSource *source = new QXmlInputSource(&opsFile);
154 OpsHandler *opsHandler = new OpsHandler(*this);
155 XmlErrorHandler *errorHandler = new XmlErrorHandler();
156 reader.setContentHandler(opsHandler);
157 reader.setErrorHandler(errorHandler);
158 ret = reader.parse(source);
163 // Initially, put all content items in the chapter list.
164 // This will be refined by parsing the NCX file later
169 QStringList coverKeys;
170 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
171 foreach (QString key, coverKeys) {
172 if (content.contains(key)) {
173 coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
177 if (coverPath.isEmpty()) {
179 QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
180 if (QFileInfo(coverJpeg).exists()) {
181 coverPath = coverJpeg;
184 if (!coverPath.isEmpty()) {
185 qDebug() << "Loading cover image from" << coverPath;
186 cover = makeCover(coverPath);
189 // If there is an "ncx" item in content, parse it: That's the real table of
192 if (content.contains("ncx")) {
193 ncxFileName = content["ncx"].href;
194 } else if (content.contains("ncxtoc")) {
195 ncxFileName = content["ncxtoc"].href;
196 } else if (content.contains("toc")) {
197 ncxFileName = content["toc"].href;
199 qDebug() << "No NCX table of contents";
201 if (!ncxFileName.isEmpty()) {
202 qDebug() << "Parsing NCX file" << ncxFileName;
203 QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
204 source = new QXmlInputSource(&ncxFile);
205 NcxHandler *ncxHandler = new NcxHandler(*this);
206 errorHandler = new XmlErrorHandler();
207 reader.setContentHandler(ncxHandler);
208 reader.setErrorHandler(errorHandler);
209 ret = reader.parse(source);
215 // Calculate book part sizes
217 foreach (QString part, parts) {
218 QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
219 content[part].size = info.size();
220 size += content[part].size;
226 bool Book::clearDir(const QString &dir)
232 QDirIterator i(dir, QDirIterator::Subdirectories);
233 while (i.hasNext()) {
234 QString entry = i.next();
235 if (entry.endsWith("/.") || entry.endsWith("/..")) {
238 QFileInfo info(entry);
240 if (!clearDir(entry)) {
245 if (!QFile::remove(entry)) {
246 qCritical() << "Book::clearDir: Could not remove" << entry;
247 // FIXME: To be investigated: This is happening too often
272 qDebug() << "path" << path();
274 QVariantHash data = BookDb::instance()->load(path());
275 title = data["title"].toString();
277 creators = data["creators"].toStringList();
278 date = data["date"].toString();
279 publisher = data["publisher"].toString();
280 datePublished = data["datepublished"].toString();
281 subject = data["subject"].toString();
282 source = data["source"].toString();
283 rights = data["rights"].toString();
284 mLastBookmark.part = data["lastpart"].toInt();
285 mLastBookmark.pos = data["lastpos"].toReal();
286 cover = data["cover"].value<QImage>().scaled(COVER_WIDTH,
287 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
288 if (cover.isNull()) {
289 cover = makeCover(":/icons/book.png");
291 int size = data["bookmarks"].toInt();
292 for (int i = 0; i < size; i++) {
293 int part = data[QString("bookmark%1part").arg(i)].toInt();
294 qreal pos = data[QString("bookmark%1pos").arg(i)].toReal();
295 QString note = data[QString("bookmark%1note").arg(i)].toString();
296 mBookmarks.append(Bookmark(part, pos, note));
305 data["title"] = title;
306 data["creators"] = creators;
308 data["publisher"] = publisher;
309 data["datepublished"] = datePublished;
310 data["subject"] = subject;
311 data["source"] = source;
312 data["rights"] = rights;
313 data["lastpart"] = mLastBookmark.part;
314 data["lastpos"] = mLastBookmark.pos;
315 data["cover"] = cover;
316 data["bookmarks"] = mBookmarks.size();
317 for (int i = 0; i < mBookmarks.size(); i++) {
318 data[QString("bookmark%1part").arg(i)] = mBookmarks[i].part;
319 data[QString("bookmark%1pos").arg(i)] = mBookmarks[i].pos;
320 data[QString("bookmark%1note").arg(i)] = mBookmarks[i].note;
322 BookDb::instance()->save(path(), data);
325 void Book::setLastBookmark(int part, qreal position)
328 qDebug() << "part" << part << "position" << position;
329 mLastBookmark.part = part;
330 mLastBookmark.pos = position;
334 Book::Bookmark Book::lastBookmark() const
336 return Book::Bookmark(mLastBookmark);
339 void Book::addBookmark(int part, qreal position, const QString ¬e)
341 mBookmarks.append(Bookmark(part, position, note));
342 qSort(mBookmarks.begin(), mBookmarks.end());
346 void Book::deleteBookmark(int index)
348 mBookmarks.removeAt(index);
352 QList<Book::Bookmark> Book::bookmarks() const
357 QString Book::opsPath()
362 QFile container(tmpDir() + "/META-INF/container.xml");
363 qDebug() << container.fileName();
364 QXmlSimpleReader reader;
365 QXmlInputSource *source = new QXmlInputSource(&container);
366 ContainerHandler *containerHandler = new ContainerHandler();
367 XmlErrorHandler *errorHandler = new XmlErrorHandler();
368 reader.setContentHandler(containerHandler);
369 reader.setErrorHandler(errorHandler);
370 if (reader.parse(source)) {
371 ret = tmpDir() + "/" + containerHandler->rootFile;
372 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
373 qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
376 delete containerHandler;
381 QString Book::rootPath() const
386 QString Book::name() const
390 if (creators.length()) {
391 ret += "\nBy " + creators[0];
392 for (int i = 1; i < creators.length(); i++) {
393 ret += ", " + creators[i];
402 QString Book::shortName() const
404 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
407 int Book::chapterFromPart(int index)
411 QString partId = parts[index];
412 QString partHref = content[partId].href;
414 for (int i = 0; i < chapters.size(); i++) {
415 QString id = chapters[i];
416 QString href = content[id].href;
417 QString baseRef(href);
418 QUrl url(QString("file://") + href);
419 if (url.hasFragment()) {
420 QString fragment = url.fragment();
421 baseRef.chop(fragment.length() + 1);
423 if (baseRef == partHref) {
425 // Don't break, keep looking
432 int Book::partFromChapter(int index, QString &fragment)
434 Trace t("Book::partFromChapter");
436 QString id = chapters[index];
437 QString href = content[id].href;
438 int hashPos = href.indexOf("#");
440 fragment = href.mid(hashPos);
441 href = href.left(hashPos);
444 qDebug() << "Chapter" << index;
445 qDebug() << " id" << id;
446 qDebug() << " href" << href;
447 qDebug() << " fragment" << fragment;
449 for (int i = 0; i < parts.size(); i++) {
450 QString partId = parts[i];
451 if (content[partId].href == href) {
452 qDebug() << "Part index for" << href << "is" << i;
457 qWarning() << "Book::partFromChapter: Could not find part index for"
462 qreal Book::getProgress(int part, qreal position)
464 Q_ASSERT(part < parts.size());
467 for (int i = 0; i < part; i++) {
469 partSize += content[key].size;
472 partSize += content[key].size * position;
473 return partSize / (qreal)size;
476 bool Book::extractMetaData()
478 QStringList excludedExtensions;
479 excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm";
480 return extract(excludedExtensions);
489 // Load book from old database (QSettings)
492 QString key = "book/" + path() + "/";
493 title = settings.value(key + "title").toString();
495 creators = settings.value(key + "creators").toStringList();
496 date = settings.value(key + "date").toString();
497 publisher = settings.value(key + "publisher").toString();
498 datePublished = settings.value(key + "datepublished").toString();
499 subject = settings.value(key + "subject").toString();
500 source = settings.value(key + "source").toString();
501 rights = settings.value(key + "rights").toString();
502 mLastBookmark.part = settings.value(key + "lastpart").toInt();
503 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
504 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
505 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
506 if (cover.isNull()) {
507 cover = makeCover(":/icons/book.png");
509 int size = settings.value(key + "bookmarks").toInt();
510 for (int i = 0; i < size; i++) {
511 int part = settings.value(key + "bookmark" + QString::number(i) +
513 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
515 qDebug() << QString("Bookmark %1 at part %2, %3").
516 arg(i).arg(part).arg(pos);
517 mBookmarks.append(Bookmark(part, pos));
520 // Save book to new database
528 BookDb::instance()->remove(path());