52727acf74103e9d683d82e18a1ae931ab9bc941
[dorian] / book.cpp
1 #include <QDir>
2 #include <QString>
3 #include <QDebug>
4 #include <QtXml>
5 #include <qtextdocument.h>  // Qt::escape is currently defined here...
6 #include <QDirIterator>
7 #include <QFileInfo>
8 #include <QtAlgorithms>
9
10 #include "book.h"
11 #include "opshandler.h"
12 #include "xmlerrorhandler.h"
13 #include "extractzip.h"
14 #include "library.h"
15 #include "containerhandler.h"
16 #include "ncxhandler.h"
17
18 Book::Book()
19 {
20 }
21
22 Book::Book(const QString &path_)
23 {
24     mPath = "";
25     if (path_ != "") {
26         QFileInfo info(path_);
27         mPath = info.absoluteFilePath();
28         title = info.baseName();
29         cover = QIcon(":/icons/book.png");
30     }
31 }
32
33 QString Book::path() const
34 {
35     return mPath;
36 }
37
38 void Book::open()
39 {
40     qDebug() << "Book::open" << path();
41     close();
42     clear();
43     if (path() == "") {
44         title = "No book";
45         fail("", "No book");
46     }
47     else if (!extract()) {
48         fail("Could not extract content of " + path() + ".");
49     }
50     else if (!parse()) {
51         fail("Could not parse content of " + path() + ".");
52     }
53     else {
54         save();
55     }
56 }
57
58 void Book::close()
59 {
60     qDebug() << "Book::close";
61     content.clear();
62     toc.clear();
63     QDir::setCurrent(QDir::rootPath());
64     clearDir(tmpDir());
65 }
66
67 QString Book::tmpDir() const
68 {
69     return QDir::tempPath() + "/dorian/book";
70 }
71
72 void Book::fail(const QString &details, const QString &error)
73 {
74     close();
75
76     toc.append("error");
77     QString errorPage = "<html><head><title>" + Qt::escape(error) +
78         "</title></head><body><h1>" + Qt::escape(error) + "</h1><p>" +
79         Qt::escape(details) + "</p></body></html>";
80     content["error"].href = errorPage;
81     content["error"].name = "Error";
82 }
83
84 bool Book::extract()
85 {
86     bool ret = false;
87     QString tmp = tmpDir();
88     qDebug() << "Book::extract: Extracting" << mPath << "to" << tmp;
89
90     QDir::setCurrent(QDir::rootPath());
91     if (!clearDir(tmp)) {
92         qCritical() << "*** Book::extract: Failed to remove" << tmp;
93         return false;
94     }
95     QDir d;
96     if (!d.mkpath(tmp)) {
97         qCritical() << "*** Book::extract: Could not create" << tmp;
98         return false;
99     }
100
101     // If book comes from resource, copy it to the temporary directory first
102     QString bookPath = path();
103     if (bookPath.startsWith(":/books/")) {
104         QFile src(bookPath);
105         QString dst(tmp + "/book.epub");
106         if (!src.copy(dst)) {
107             qCritical() << "*** Book::extract: Failed to copy built-in book to"
108                     << dst;
109             return false;
110         }
111         bookPath = dst;
112     }
113
114     QString oldDir = QDir::currentPath();
115     if (!QDir::setCurrent(tmp)) {
116         qCritical() << "*** Book::extract: Could not change to" << tmp;
117         return false;
118     }
119     ret = extractZip(bookPath);
120     if (!ret) {
121         qCritical() << "*** Book::extract: Extracting ZIP failed";
122     }
123     QDir::setCurrent(oldDir);
124     return ret;
125 }
126
127 bool Book::parse()
128 {
129     qDebug() << "Book::parse";
130
131     bool ret = false;
132     QString opsFileName = opsPath();
133     qDebug() << " Parsing OPS file" << opsFileName;
134     QFile opsFile(opsFileName);
135     QXmlSimpleReader reader;
136     QXmlInputSource *source = new QXmlInputSource(&opsFile);
137     OpsHandler *opsHandler = new OpsHandler(*this);
138     XmlErrorHandler *errorHandler = new XmlErrorHandler();
139     reader.setContentHandler(opsHandler);
140     reader.setErrorHandler(errorHandler);
141     ret = reader.parse(source);
142     delete errorHandler;
143     delete opsHandler;
144     delete source;
145
146     // If there is an "ncx" item in content, parse it: That's the real table of
147     // contents
148     if (content.contains("ncx")) {
149         QString ncxFileName = content["ncx"].href;
150         qDebug() << " Parsing NCX file" << ncxFileName;
151         QFile ncxFile(ncxFileName);
152         source = new QXmlInputSource(&ncxFile);
153         NcxHandler *ncxHandler = new NcxHandler(*this);
154         errorHandler = new XmlErrorHandler();
155         reader.setContentHandler(ncxHandler);
156         reader.setErrorHandler(errorHandler);
157         ret = reader.parse(source);
158         delete ncxHandler;
159         delete errorHandler;
160         delete source;
161     }
162
163     return ret;
164 }
165
166 bool Book::clearDir(const QString &dir)
167 {
168     QDir d(dir);
169     if (!d.exists()) {
170         return true;
171     }
172     QDirIterator i(dir, QDirIterator::Subdirectories);
173     while (i.hasNext()) {
174         QString entry = i.next();
175         if (entry.endsWith("/.") || entry.endsWith("/..")) {
176             continue;
177         }
178         QFileInfo info(entry);
179         if (info.isDir()) {
180             if (!clearDir(entry)) {
181                 return false;
182             }
183         }
184         else {
185             if (!QFile::remove(entry)) {
186                 qCritical() << "*** Book::clearDir: Could not remove" << entry;
187                 // FIXME: To be investigated: This is happening too often
188                 // return false;
189             }
190         }
191     }
192     (void)d.rmpath(dir);
193     return true;
194 }
195
196 void Book::clear()
197 {
198     close();
199     title = "";
200     creators.clear();
201     date = "";
202     publisher = "";
203     datePublished = "";
204     subject = "";
205     source = "";
206     rights = "";
207 }
208
209 void Book::load()
210 {
211     qDebug() << "Book::load" << path();
212     QSettings settings;
213     QString key = "book/" + path() + "/";
214     qDebug() << " key" << key;
215
216     // Load book info
217     title = settings.value(key + "title").toString();
218     qDebug() << " title" << title;
219     creators = settings.value(key + "creators").toStringList();
220     date = settings.value(key + "date").toString();
221     publisher = settings.value(key + "publisher").toString();
222     datePublished = settings.value(key + "datepublished").toString();
223     subject = settings.value(key + "subject").toString();
224     source = settings.value(key + "source").toString();
225     rights = settings.value(key + "rights").toString();
226     mLastBookmark.chapter = settings.value(key + "lastchapter").toInt();
227     mLastBookmark.pos = settings.value(key + "lastpos").toReal();
228
229     // Load bookmarks
230     int size = settings.value(key + "bookmarks").toInt();
231     for (int i = 0; i < size; i++) {
232         int chapter = settings.value(key + "bookmark" + QString::number(i) +
233                                      "/chapter").toInt();
234         qreal pos = settings.value(key + "bookmark" + QString::number(i) +
235                                    "/pos").toReal();
236         qDebug() << " Bookmark" << i << "at" << chapter << "," << pos;
237         mBookmarks.append(Bookmark(chapter, pos));
238     }
239 }
240
241 void Book::save()
242 {
243     qDebug() << "Book::save";
244     QSettings settings;
245     QString key = "book/" + path() + "/";
246     qDebug() << " key" << key;
247
248     // Save book info
249     settings.setValue(key + "title", title);
250     qDebug() << " title" << title;
251     settings.setValue(key + "creators", creators);
252     settings.setValue(key + "date", date);
253     settings.setValue(key + "publisher", publisher);
254     settings.setValue(key + "datepublished", datePublished);
255     settings.setValue(key + "subject", subject);
256     settings.setValue(key + "source", source);
257     settings.setValue(key + "rights", rights);
258     settings.setValue(key + "lastchapter", mLastBookmark.chapter);
259     settings.setValue(key + "lastpos", mLastBookmark.pos);
260
261     // Save bookmarks
262     settings.setValue(key + "bookmarks", mBookmarks.size());
263     for (int i = 0; i < mBookmarks.size(); i++) {
264         qDebug() << " Bookmark" << i << "at" << mBookmarks[i].chapter << ","
265                 << mBookmarks[i].pos;
266         settings.setValue(key + "bookmark" + QString::number(i) + "/chapter",
267                           mBookmarks[i].chapter);
268         settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
269                           mBookmarks[i].pos);
270     }
271 }
272
273 void Book::setLastBookmark(int chapter, qreal position)
274 {
275     mLastBookmark.chapter = chapter;
276     mLastBookmark.pos = position;
277     save();
278 }
279
280 Book::Bookmark Book::lastBookmark() const
281 {
282     return Book::Bookmark(mLastBookmark);
283 }
284
285 void Book::addBookmark(int chapter, qreal position)
286 {
287     mBookmarks.append(Bookmark(chapter, position));
288     qSort(mBookmarks.begin(), mBookmarks.end());
289     save();
290 }
291
292 void Book::deleteBookmark(int index)
293 {
294     mBookmarks.removeAt(index);
295     save();
296 }
297
298 QList<Book::Bookmark> Book::bookmarks() const
299 {
300     return mBookmarks;
301 }
302
303 QString Book::opsPath()
304 {
305     QString ret;
306
307     QFile container(tmpDir() + "/META-INF/container.xml");
308     qDebug() << "Book::opsPath" << container.fileName();
309     QXmlSimpleReader reader;
310     QXmlInputSource *source = new QXmlInputSource(&container);
311     ContainerHandler *containerHandler = new ContainerHandler();
312     XmlErrorHandler *errorHandler = new XmlErrorHandler();
313     reader.setContentHandler(containerHandler);
314     reader.setErrorHandler(errorHandler);
315     if (reader.parse(source)) {
316         ret = tmpDir() + "/" + containerHandler->rootFile;
317         mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
318         qDebug() << " OSP path" << ret;
319         qDebug() << " Root dir" << mRootPath;
320     }
321     delete errorHandler;
322     delete containerHandler;
323     delete source;
324     return ret;
325 }
326
327 QString Book::rootPath() const
328 {
329     return mRootPath;
330 }
331
332 QString Book::name() const
333 {
334     if (title != "") {
335         return title;
336     } else {
337         return path();
338     }
339 }