5 #include <qtextdocument.h> // Qt::escape is currently defined here...
6 #include <QDirIterator>
8 #include <QtAlgorithms>
9 #include <QCryptographicHash>
12 #include "opshandler.h"
13 #include "xmlerrorhandler.h"
14 #include "extractzip.h"
16 #include "containerhandler.h"
17 #include "ncxhandler.h"
20 const int COVER_WIDTH = 53;
21 const int COVER_HEIGHT = 59;
23 static QImage makeCover(const QString &path)
25 return QImage(path).scaled(COVER_WIDTH, COVER_HEIGHT,
26 Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
27 scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
30 Book::Book(const QString &p, QObject *parent): QObject(parent)
35 mPath = info.absoluteFilePath();
36 title = info.baseName();
37 cover = makeCover(":/icons/book.png");
42 QString Book::path() const
49 Trace t("Book::open");
53 if (path().isEmpty()) {
57 if (!extract(QStringList())) {
70 Trace t("Book::peek");
74 if (path().isEmpty()) {
78 if (!extractMetaData()) {
90 Trace t("Book::close");
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)
106 Trace t("Book::extract");
108 QString tmp = tmpDir();
109 qDebug() << "Extracting" << mPath << "to" << tmp;
111 QDir::setCurrent(QDir::rootPath());
112 if (!clearDir(tmp)) {
113 qCritical() << "Book::extract: Failed to remove" << tmp;
118 if (!d.mkpath(tmp)) {
119 qCritical() << "Book::extract: Could not create" << tmp;
124 // If book comes from resource, copy it to the temporary directory first
125 QString bookPath = path();
126 if (bookPath.startsWith(":/books/")) {
128 QString dst(QDir(tmp).absoluteFilePath("book.epub"));
129 if (!src.copy(dst)) {
130 qCritical() << "Book::extract: Failed to copy built-in book"
131 << bookPath << "to" << dst;
137 QString oldDir = QDir::currentPath();
138 if (!QDir::setCurrent(tmp)) {
139 qCritical() << "Book::extract: Could not change to" << tmp;
142 ret = extractZip(bookPath, excludedExtensions);
144 qCritical() << "Book::extract: Extracting ZIP failed";
146 QDir::setCurrent(oldDir);
152 Trace t("Book::parse");
156 QString opsFileName = opsPath();
157 qDebug() << "Parsing OPS file" << opsFileName;
158 QFile opsFile(opsFileName);
159 QXmlSimpleReader reader;
160 QXmlInputSource *source = new QXmlInputSource(&opsFile);
161 OpsHandler *opsHandler = new OpsHandler(*this);
162 XmlErrorHandler *errorHandler = new XmlErrorHandler();
163 reader.setContentHandler(opsHandler);
164 reader.setErrorHandler(errorHandler);
165 ret = reader.parse(source);
170 // Initially, put all content items in the chapter list.
171 // This will be refined by parsing the NCX file later
176 QStringList coverKeys;
177 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
178 foreach (QString key, coverKeys) {
179 if (content.contains(key)) {
180 coverPath = QDir(rootPath()).absoluteFilePath(content[key].href);
184 if (coverPath.isEmpty()) {
186 QString coverJpeg = QDir(rootPath()).absoluteFilePath("cover.jpg");
187 if (QFileInfo(coverJpeg).exists()) {
188 coverPath = coverJpeg;
191 if (!coverPath.isEmpty()) {
192 qDebug() << "Loading cover image from" << coverPath;
193 cover = makeCover(coverPath);
196 // If there is an "ncx" item in content, parse it: That's the real table of
199 if (content.contains("ncx")) {
200 ncxFileName = content["ncx"].href;
201 } else if (content.contains("ncxtoc")) {
202 ncxFileName = content["ncxtoc"].href;
203 } else if (content.contains("toc")) {
204 ncxFileName = content["toc"].href;
206 qDebug() << "No NCX table of contents";
208 if (!ncxFileName.isEmpty()) {
209 qDebug() << "Parsing NCX file" << ncxFileName;
210 QFile ncxFile(QDir(rootPath()).absoluteFilePath(ncxFileName));
211 source = new QXmlInputSource(&ncxFile);
212 NcxHandler *ncxHandler = new NcxHandler(*this);
213 errorHandler = new XmlErrorHandler();
214 reader.setContentHandler(ncxHandler);
215 reader.setErrorHandler(errorHandler);
216 ret = reader.parse(source);
222 // Calculate book part sizes
224 foreach (QString part, parts) {
225 QFileInfo info(QDir(rootPath()).absoluteFilePath(content[part].href));
226 content[part].size = info.size();
227 size += content[part].size;
233 bool Book::clearDir(const QString &dir)
239 QDirIterator i(dir, QDirIterator::Subdirectories);
240 while (i.hasNext()) {
241 QString entry = i.next();
242 if (entry.endsWith("/.") || entry.endsWith("/..")) {
245 QFileInfo info(entry);
247 if (!clearDir(entry)) {
252 if (!QFile::remove(entry)) {
253 qCritical() << "Book::clearDir: Could not remove" << entry;
254 // FIXME: To be investigated: This is happening too often
278 Trace t("Book::load");
279 qDebug() << "path" << path();
281 QString key = "book/" + path() + "/";
282 qDebug() << "key" << key;
285 title = settings.value(key + "title").toString();
287 creators = settings.value(key + "creators").toStringList();
288 date = settings.value(key + "date").toString();
289 publisher = settings.value(key + "publisher").toString();
290 datePublished = settings.value(key + "datepublished").toString();
291 subject = settings.value(key + "subject").toString();
292 source = settings.value(key + "source").toString();
293 rights = settings.value(key + "rights").toString();
294 mLastBookmark.part = settings.value(key + "lastpart").toInt();
295 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
296 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
297 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
298 if (cover.isNull()) {
299 cover = makeCover(":/icons/book.png");
303 int size = settings.value(key + "bookmarks").toInt();
304 for (int i = 0; i < size; i++) {
305 int part = settings.value(key + "bookmark" + QString::number(i) +
307 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
309 qDebug() << QString("Bookmark %1 at part %2, %3").
310 arg(i).arg(part).arg(pos);
311 mBookmarks.append(Bookmark(part, pos));
317 Trace t("Book::save");
319 QString key = "book/" + path() + "/";
320 qDebug() << "key" << key;
323 settings.setValue(key + "title", title);
324 qDebug() << "title" << title;
325 settings.setValue(key + "creators", creators);
326 settings.setValue(key + "date", date);
327 settings.setValue(key + "publisher", publisher);
328 settings.setValue(key + "datepublished", datePublished);
329 settings.setValue(key + "subject", subject);
330 settings.setValue(key + "source", source);
331 settings.setValue(key + "rights", rights);
332 settings.setValue(key + "lastpart", mLastBookmark.part);
333 settings.setValue(key + "lastpos", mLastBookmark.pos);
334 settings.setValue(key + "cover", cover);
337 settings.setValue(key + "bookmarks", mBookmarks.size());
338 for (int i = 0; i < mBookmarks.size(); i++) {
339 qDebug() << QString("Bookmark %1 at %2, %3").
340 arg(i).arg(mBookmarks[i].part).arg(mBookmarks[i].pos);
341 settings.setValue(key + "bookmark" + QString::number(i) + "/part",
343 settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
348 void Book::setLastBookmark(int part, qreal position)
350 mLastBookmark.part = part;
351 mLastBookmark.pos = position;
355 Book::Bookmark Book::lastBookmark() const
357 return Book::Bookmark(mLastBookmark);
360 void Book::addBookmark(int part, qreal position)
362 mBookmarks.append(Bookmark(part, position));
363 qSort(mBookmarks.begin(), mBookmarks.end());
367 void Book::deleteBookmark(int index)
369 mBookmarks.removeAt(index);
373 QList<Book::Bookmark> Book::bookmarks() const
378 QString Book::opsPath()
380 Trace t("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() const
407 QString Book::name() const
411 if (creators.length()) {
412 ret += "\nBy " + creators[0];
413 for (int i = 1; i < creators.length(); i++) {
414 ret += ", " + creators[i];
423 QString Book::shortName() const
425 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
428 int Book::chapterFromPart(int index)
432 QString partId = parts[index];
433 QString partHref = content[partId].href;
435 for (int i = 0; i < chapters.size(); i++) {
436 QString id = chapters[i];
437 QString href = content[id].href;
438 QString baseRef(href);
439 QUrl url(QString("file://") + href);
440 if (url.hasFragment()) {
441 QString fragment = url.fragment();
442 baseRef.chop(fragment.length() + 1);
444 if (baseRef == partHref) {
446 // Don't break, keep looking
453 int Book::partFromChapter(int index)
455 Trace t("Book::partFromChapter");
456 QString id = chapters[index];
457 QString href = content[id].href;
458 int hashPos = href.indexOf("#");
460 href = href.left(hashPos);
463 qDebug() << "Chapter" << index;
464 qDebug() << " id" << id;
465 qDebug() << " href" << href;
467 for (int i = 0; i < parts.size(); i++) {
468 QString partId = parts[i];
469 if (content[partId].href == href) {
470 qDebug() << "Part index for" << href << "is" << i;
475 qWarning() << "Book::partFromChapter: Could not find part index for"
480 qreal Book::getProgress(int part, qreal position)
482 Q_ASSERT(part < parts.size());
485 for (int i = 0; i < part; i++) {
487 partSize += content[key].size;
490 partSize += content[key].size * position;
491 return partSize / (qreal)size;
494 bool Book::extractMetaData()
496 QStringList excludedExtensions;
497 excludedExtensions << ".html" << ".xhtml" << ".xht" << ".htm";
498 return extract(excludedExtensions);
503 Trace t("Book::upgrade");