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()) {
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::tempPath() + "/dorian/" + tmpName;
105 Trace t("Book::extract");
107 QString tmp = tmpDir();
108 qDebug() << "Extracting" << mPath << "to" << tmp;
110 QDir::setCurrent(QDir::rootPath());
111 if (!clearDir(tmp)) {
112 qCritical() << "Book::extract: Failed to remove" << tmp;
116 if (!d.mkpath(tmp)) {
117 qCritical() << "Book::extract: Could not create" << tmp;
121 // If book comes from resource, copy it to the temporary directory first
122 QString bookPath = path();
123 if (bookPath.startsWith(":/books/")) {
125 QString dst(tmp + "/book.epub");
126 if (!src.copy(dst)) {
127 qCritical() << "Book::extract: Failed to copy built-in book to"
134 QString oldDir = QDir::currentPath();
135 if (!QDir::setCurrent(tmp)) {
136 qCritical() << "Book::extract: Could not change to" << tmp;
139 ret = extractZip(bookPath);
141 qCritical() << "Book::extract: Extracting ZIP failed";
143 QDir::setCurrent(oldDir);
149 Trace t("Book::parse");
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
172 QStringList coverKeys;
173 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
174 foreach (QString key, coverKeys) {
175 if (content.contains(key)) {
176 qDebug() << "Loading cover image from" << content[key].href;
177 cover = makeCover(content[key].href);
182 // If there is an "ncx" item in content, parse it: That's the real table of
184 if (content.contains("ncx")) {
185 QString ncxFileName = content["ncx"].href;
186 qDebug() << "Parsing NCX file" << ncxFileName;
187 QFile ncxFile(ncxFileName);
188 source = new QXmlInputSource(&ncxFile);
189 NcxHandler *ncxHandler = new NcxHandler(*this);
190 errorHandler = new XmlErrorHandler();
191 reader.setContentHandler(ncxHandler);
192 reader.setErrorHandler(errorHandler);
193 ret = reader.parse(source);
199 // Calculate book part sizes
201 foreach (QString part, parts) {
202 QFileInfo info(content[part].href);
203 content[part].size = info.size();
204 size += content[part].size;
210 bool Book::clearDir(const QString &dir)
216 QDirIterator i(dir, QDirIterator::Subdirectories);
217 while (i.hasNext()) {
218 QString entry = i.next();
219 if (entry.endsWith("/.") || entry.endsWith("/..")) {
222 QFileInfo info(entry);
224 if (!clearDir(entry)) {
229 if (!QFile::remove(entry)) {
230 qCritical() << "Book::clearDir: Could not remove" << entry;
231 // FIXME: To be investigated: This is happening too often
255 Trace t("Book::load");
256 qDebug() << "path" << path();
258 QString key = "book/" + path() + "/";
259 qDebug() << "key" << key;
262 title = settings.value(key + "title").toString();
264 creators = settings.value(key + "creators").toStringList();
265 date = settings.value(key + "date").toString();
266 publisher = settings.value(key + "publisher").toString();
267 datePublished = settings.value(key + "datepublished").toString();
268 subject = settings.value(key + "subject").toString();
269 source = settings.value(key + "source").toString();
270 rights = settings.value(key + "rights").toString();
271 mLastBookmark.part = settings.value(key + "lastpart").toInt();
272 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
273 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
274 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
275 if (cover.isNull()) {
276 cover = makeCover(":/icons/book.png");
280 int size = settings.value(key + "bookmarks").toInt();
281 for (int i = 0; i < size; i++) {
282 int part = settings.value(key + "bookmark" + QString::number(i) +
284 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
286 qDebug() << QString("Bookmark %1 at part %2, %3").
287 arg(i).arg(part).arg(pos);
288 mBookmarks.append(Bookmark(part, pos));
294 Trace t("Book::save");
296 QString key = "book/" + path() + "/";
297 qDebug() << "key" << key;
300 settings.setValue(key + "title", title);
301 qDebug() << "title" << title;
302 settings.setValue(key + "creators", creators);
303 settings.setValue(key + "date", date);
304 settings.setValue(key + "publisher", publisher);
305 settings.setValue(key + "datepublished", datePublished);
306 settings.setValue(key + "subject", subject);
307 settings.setValue(key + "source", source);
308 settings.setValue(key + "rights", rights);
309 settings.setValue(key + "lastpart", mLastBookmark.part);
310 settings.setValue(key + "lastpos", mLastBookmark.pos);
311 settings.setValue(key + "cover", cover);
314 settings.setValue(key + "bookmarks", mBookmarks.size());
315 for (int i = 0; i < mBookmarks.size(); i++) {
316 qDebug() << QString("Bookmark %1 at %2, %3").
317 arg(i).arg(mBookmarks[i].part).arg(mBookmarks[i].pos);
318 settings.setValue(key + "bookmark" + QString::number(i) + "/part",
320 settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
325 void Book::setLastBookmark(int part, qreal position)
327 mLastBookmark.part = part;
328 mLastBookmark.pos = position;
332 Book::Bookmark Book::lastBookmark() const
334 return Book::Bookmark(mLastBookmark);
337 void Book::addBookmark(int part, qreal position)
339 mBookmarks.append(Bookmark(part, position));
340 qSort(mBookmarks.begin(), mBookmarks.end());
344 void Book::deleteBookmark(int index)
346 mBookmarks.removeAt(index);
350 QList<Book::Bookmark> Book::bookmarks() const
355 QString Book::opsPath()
357 Trace t("Book::opsPath");
360 QFile container(tmpDir() + "/META-INF/container.xml");
361 qDebug() << container.fileName();
362 QXmlSimpleReader reader;
363 QXmlInputSource *source = new QXmlInputSource(&container);
364 ContainerHandler *containerHandler = new ContainerHandler();
365 XmlErrorHandler *errorHandler = new XmlErrorHandler();
366 reader.setContentHandler(containerHandler);
367 reader.setErrorHandler(errorHandler);
368 if (reader.parse(source)) {
369 ret = tmpDir() + "/" + containerHandler->rootFile;
370 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
371 qDebug() << "OSP path" << ret << "\nRoot dir" << mRootPath;
374 delete containerHandler;
379 QString Book::rootPath() const
384 QString Book::name() const
388 if (creators.length()) {
389 ret += "\nBy " + creators[0];
390 for (int i = 1; i < creators.length(); i++) {
391 ret += ", " + creators[i];
400 QString Book::shortName() const
402 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
405 int Book::chapterFromPart(int index)
409 QString partId = parts[index];
410 QString partHref = content[partId].href;
412 for (int i = 0; i < chapters.size(); i++) {
413 QString id = chapters[i];
414 QString href = content[id].href;
415 QString baseRef(href);
416 QUrl url(QString("file://") + href);
417 if (url.hasFragment()) {
418 QString fragment = url.fragment();
419 baseRef.chop(fragment.length() + 1);
421 if (baseRef == partHref) {
423 // Don't break, keep looking
430 int Book::partFromChapter(int index)
432 Trace t("Book::partFromChapter");
433 QString id = chapters[index];
434 QString href = content[id].href;
435 QString baseRef(href);
436 QUrl url(QString("file://") + href);
437 if (url.hasFragment()) {
438 QString fragment = url.fragment();
439 baseRef.chop(fragment.length() + 1);
442 // Swipe through all content items to find the one matching the chapter href
443 // FIXME: Do we need to index content items by href, too?
446 foreach (contentKey, content.keys()) {
447 if (contentKey == id) {
450 if (content[contentKey].href == baseRef) {
452 qDebug() << QString("Key for %1 is %2").arg(baseRef).arg(contentKey);
457 qDebug() << "Could not find key for" << baseRef;
460 int partIndex = parts.indexOf(contentKey);
461 if (partIndex == -1) {
463 << "Book::partFromChapter: Could not find part index of chapter"
469 qreal Book::getProgress(int part, qreal position)
471 Q_ASSERT(part < parts.size());
474 for (int i = 0; i < part; i++) {
476 partSize += content[key].size;
479 partSize += content[key].size * position;
480 return partSize / (qreal)size;
483 bool Book::extractMetaData()