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