5 #include <qtextdocument.h> // Qt::escape is currently defined here...
6 #include <QDirIterator>
8 #include <QtAlgorithms>
11 #include "opshandler.h"
12 #include "xmlerrorhandler.h"
13 #include "extractzip.h"
15 #include "containerhandler.h"
16 #include "ncxhandler.h"
19 const int COVER_WIDTH = 53;
20 const int COVER_HEIGHT = 59;
22 Book::Book(const QString &p, QObject *parent): QObject(parent)
27 mPath = info.absoluteFilePath();
28 title = info.baseName();
29 cover = QImage(":/icons/book.png").scaled(COVER_WIDTH, COVER_HEIGHT,
30 Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
31 scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
35 QString Book::path() const
42 Trace t("Book::open");
46 if (path().isEmpty()) {
63 Trace t("Book::close");
66 QDir::setCurrent(QDir::rootPath());
70 QString Book::tmpDir() const
72 return QDir::tempPath() + "/dorian/book";
77 Trace t("Book::extract");
79 QString tmp = tmpDir();
80 t.trace("Extracting " + mPath + " to " + tmp);
82 QDir::setCurrent(QDir::rootPath());
84 qCritical() << "Book::extract: Failed to remove" << tmp;
89 qCritical() << "Book::extract: Could not create" << tmp;
93 // If book comes from resource, copy it to the temporary directory first
94 QString bookPath = path();
95 if (bookPath.startsWith(":/books/")) {
97 QString dst(tmp + "/book.epub");
99 qCritical() << "Book::extract: Failed to copy built-in book to"
106 QString oldDir = QDir::currentPath();
107 if (!QDir::setCurrent(tmp)) {
108 qCritical() << "Book::extract: Could not change to" << tmp;
111 ret = extractZip(bookPath);
113 qCritical() << "Book::extract: Extracting ZIP failed";
115 QDir::setCurrent(oldDir);
121 Trace t("Book::parse");
125 QString opsFileName = opsPath();
126 t.trace("Parsing OPS file" + opsFileName);
127 QFile opsFile(opsFileName);
128 QXmlSimpleReader reader;
129 QXmlInputSource *source = new QXmlInputSource(&opsFile);
130 OpsHandler *opsHandler = new OpsHandler(*this);
131 XmlErrorHandler *errorHandler = new XmlErrorHandler();
132 reader.setContentHandler(opsHandler);
133 reader.setErrorHandler(errorHandler);
134 ret = reader.parse(source);
139 // Initially, put all content items in the chapter list.
140 // This will be refined by parsing the NCX file later
144 QStringList coverKeys;
145 coverKeys << "cover-image" << "img-cover-jpeg" << "cover";
146 foreach (QString key, coverKeys) {
147 if (content.contains(key)) {
148 t.trace("Loading cover image from " + content[key].href);
149 cover = QImage(content[key].href).scaled(COVER_WIDTH, COVER_HEIGHT,
150 Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
151 scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
156 // If there is an "ncx" item in content, parse it: That's the real table of
158 if (content.contains("ncx")) {
159 QString ncxFileName = content["ncx"].href;
160 t.trace("Parsing NCX file " + ncxFileName);
161 QFile ncxFile(ncxFileName);
162 source = new QXmlInputSource(&ncxFile);
163 NcxHandler *ncxHandler = new NcxHandler(*this);
164 errorHandler = new XmlErrorHandler();
165 reader.setContentHandler(ncxHandler);
166 reader.setErrorHandler(errorHandler);
167 ret = reader.parse(source);
173 // Calculate book part sizes
175 foreach (QString part, parts) {
176 QFileInfo info(content[part].href);
177 content[part].size = info.size();
178 size += content[part].size;
179 t.trace(QString("Size of part %1: %2").arg(part).arg(content[part].size));
185 bool Book::clearDir(const QString &dir)
191 QDirIterator i(dir, QDirIterator::Subdirectories);
192 while (i.hasNext()) {
193 QString entry = i.next();
194 if (entry.endsWith("/.") || entry.endsWith("/..")) {
197 QFileInfo info(entry);
199 if (!clearDir(entry)) {
204 if (!QFile::remove(entry)) {
205 qCritical() << "Book::clearDir: Could not remove" << entry;
206 // FIXME: To be investigated: This is happening too often
230 Trace t("Book::load");
231 t.trace("path: " + path());
233 QString key = "book/" + path() + "/";
234 t.trace("key: " + key);
237 title = settings.value(key + "title").toString();
239 creators = settings.value(key + "creators").toStringList();
240 date = settings.value(key + "date").toString();
241 publisher = settings.value(key + "publisher").toString();
242 datePublished = settings.value(key + "datepublished").toString();
243 subject = settings.value(key + "subject").toString();
244 source = settings.value(key + "source").toString();
245 rights = settings.value(key + "rights").toString();
246 mLastBookmark.part = settings.value(key + "lastpart").toInt();
247 mLastBookmark.pos = settings.value(key + "lastpos").toReal();
248 cover = settings.value(key + "cover").value<QImage>().scaled(COVER_WIDTH,
249 COVER_HEIGHT, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
250 if (cover.isNull()) {
251 cover = QImage(":/icons/book.png").scaled(COVER_WIDTH, COVER_HEIGHT,
252 Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation).
253 scaled(COVER_WIDTH, COVER_HEIGHT, Qt::KeepAspectRatio);
257 int size = settings.value(key + "bookmarks").toInt();
258 for (int i = 0; i < size; i++) {
259 int part = settings.value(key + "bookmark" + QString::number(i) +
261 qreal pos = settings.value(key + "bookmark" + QString::number(i) +
263 t.trace(QString("Bookmark %1 at part %2, %3").
264 arg(i).arg(part).arg(pos));
265 mBookmarks.append(Bookmark(part, pos));
271 Trace t("Book::save");
273 QString key = "book/" + path() + "/";
274 t.trace("key: " + key);
277 settings.setValue(key + "title", title);
278 t.trace("title: " + title);
279 settings.setValue(key + "creators", creators);
280 settings.setValue(key + "date", date);
281 settings.setValue(key + "publisher", publisher);
282 settings.setValue(key + "datepublished", datePublished);
283 settings.setValue(key + "subject", subject);
284 settings.setValue(key + "source", source);
285 settings.setValue(key + "rights", rights);
286 settings.setValue(key + "lastpart", mLastBookmark.part);
287 settings.setValue(key + "lastpos", mLastBookmark.pos);
288 settings.setValue(key + "cover", cover);
291 settings.setValue(key + "bookmarks", mBookmarks.size());
292 for (int i = 0; i < mBookmarks.size(); i++) {
293 t.trace(QString("Bookmark %1 at %2, %3").
294 arg(i).arg(mBookmarks[i].part).arg(mBookmarks[i].pos));
295 settings.setValue(key + "bookmark" + QString::number(i) + "/part",
297 settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
302 void Book::setLastBookmark(int part, qreal position)
304 mLastBookmark.part = part;
305 mLastBookmark.pos = position;
309 Book::Bookmark Book::lastBookmark() const
311 return Book::Bookmark(mLastBookmark);
314 void Book::addBookmark(int part, qreal position)
316 mBookmarks.append(Bookmark(part, position));
317 qSort(mBookmarks.begin(), mBookmarks.end());
321 void Book::deleteBookmark(int index)
323 mBookmarks.removeAt(index);
327 QList<Book::Bookmark> Book::bookmarks() const
332 QString Book::opsPath()
334 Trace t("Book::opsPath");
337 QFile container(tmpDir() + "/META-INF/container.xml");
338 t.trace(container.fileName());
339 QXmlSimpleReader reader;
340 QXmlInputSource *source = new QXmlInputSource(&container);
341 ContainerHandler *containerHandler = new ContainerHandler();
342 XmlErrorHandler *errorHandler = new XmlErrorHandler();
343 reader.setContentHandler(containerHandler);
344 reader.setErrorHandler(errorHandler);
345 if (reader.parse(source)) {
346 ret = tmpDir() + "/" + containerHandler->rootFile;
347 mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
348 t.trace("OSP path: " + ret);
349 t.trace("Root dir: " + mRootPath);
352 delete containerHandler;
357 QString Book::rootPath() const
362 QString Book::name() const
366 if (creators.length()) {
367 ret += "\nBy " + creators[0];
368 for (int i = 1; i < creators.length(); i++) {
369 ret += ", " + creators[i];
378 QString Book::shortName() const
380 return (title.isEmpty())? QFileInfo(path()).baseName(): title;
383 int Book::chapterFromPart(int index)
391 int Book::partFromChapter(int index)
393 Trace t("Book::partFromChapter");
394 QString id = chapters[index];
395 QString href = content[id].href;
396 QString baseRef(href);
397 QUrl url(QString("file://") + href);
398 if (url.hasFragment()) {
399 QString fragment = url.fragment();
400 baseRef.chop(fragment.length() + 1);
403 // Swipe through all content items to find the one matching the chapter href
404 // FIXME: Do we need to index content items by href, too?
407 foreach (contentKey, content.keys()) {
408 if (contentKey == id) {
411 if (content[contentKey].href == baseRef) {
413 t.trace(QString("Key for %1 is %2").arg(baseRef).arg(contentKey));
418 t.trace("Could not find key for " + baseRef);
421 int partIndex = parts.indexOf(contentKey);
422 if (partIndex == -1) {
424 << "Book::partFromChapter: Could not find part index of chapter"
430 qreal Book::getProgress(int part, qreal position)
432 Q_ASSERT(part < parts.size());
435 for (int i = 0; i < part; i++) {
437 partSize += content[key].size;
440 partSize += content[key].size * position;
441 return partSize / (qreal)size;