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