1 #include <iostream>
2 #include <limits>
3 #include <cerrno>
4 #include <unistd.h>
5 #include <QSocketNotifier>
6 #include <QFileInfo>
7 #ifdef __linux__
8 #include <sys/inotify.h>
9 #endif
10 #include "resourcemanager.h"
11 #include "canvas.h"
12 #include "util.h"
13 #include "kpage.h"
14 #include "worker.h"
15 #include "viewer.h"
16 #include "beamerwindow.h"
17 #include "selection.h"
18 #include "layout/layout.h"
19
20 using namespace std;
21
22
Request(int width,int index)23 Request::Request(int width, int index) {
24 for (int i = 0; i < 3; i++) {
25 this->width[i] = -1;
26 }
27 this->width[index] = width;
28 }
29
get_lowest_index()30 int Request::get_lowest_index() {
31 for (int i = 0; i < 3; i++) {
32 if (width[i] != -1) {
33 return i;
34 }
35 }
36 // will not happen
37 return 0;
38 }
39
has_index(int index)40 bool Request::has_index(int index) {
41 return width[index] != -1;
42 }
43
remove_index_ok(int index)44 bool Request::remove_index_ok(int index) {
45 width[index] = -1;
46 for (int i = 0; i < 3; i++) {
47 if (width[i] != -1) {
48 return true;
49 }
50 }
51 return false;
52 }
53
update(int width,int index)54 void Request::update(int width, int index) {
55 this->width[index] = width;
56 }
57
58
ResourceManager(const QString & file,Viewer * v)59 ResourceManager::ResourceManager(const QString &file, Viewer *v) :
60 viewer(v),
61 file(file),
62 doc(NULL),
63 center_page(0),
64 rotation(0),
65 #ifdef __linux__
66 i_notifier(NULL),
67 #endif
68 inverted_colors(false),
69 cur_jump_pos(jumplist.end()) {
70 initialize(file, QByteArray());
71 }
72
initialize(const QString & file,const QByteArray & password)73 void ResourceManager::initialize(const QString &file, const QByteArray &password) {
74 page_count = 0;
75 k_page = NULL;
76
77 doc = NULL;
78 if (!file.isNull()) {
79 doc = Poppler::Document::load(file, QByteArray(), password);
80 }
81
82 worker = new Worker(this);
83 if (viewer->get_canvas() != NULL) {
84 // on first start the canvas has not yet been constructed
85 connect(worker, SIGNAL(page_rendered(int)), viewer->get_canvas(), SLOT(page_rendered(int)), Qt::UniqueConnection);
86 connect(worker, SIGNAL(page_rendered(int)), viewer->get_beamer(), SLOT(page_rendered(int)), Qt::UniqueConnection);
87 }
88 worker->start();
89
90 // setup inotify
91 #ifdef __linux__
92 QFileInfo info(file);
93 inotify_fd = inotify_init();
94 if (inotify_fd == -1) {
95 cerr << "inotify_init: " << strerror(errno) << endl;
96 } else {
97 i_notifier = new QSocketNotifier(inotify_fd, QSocketNotifier::Read, this);
98 connect(i_notifier, SIGNAL(activated(int)), this, SLOT(inotify_slot()),
99 Qt::UniqueConnection);
100
101 inotify_wd = inotify_add_watch(inotify_fd, info.path().toUtf8().constData(), IN_CLOSE_WRITE | IN_MOVED_TO);
102 if (inotify_wd == -1) {
103 cerr << "inotify_add_watch: " << strerror(errno) << endl;
104 }
105 }
106 #endif
107
108 if (doc == NULL) {
109 // poppler already prints a debug message
110 // cerr << "failed to open file" << endl;
111 return;
112 }
113 if (doc->isLocked()) {
114 // poppler already prints a debug message
115 // cerr << "missing password" << endl;
116 return;
117 }
118 doc->setRenderHint(Poppler::Document::Antialiasing, true);
119 doc->setRenderHint(Poppler::Document::TextAntialiasing, true);
120 doc->setRenderHint(Poppler::Document::TextHinting, true);
121 doc->setRenderHint(Poppler::Document::TextSlightHinting, true);
122 // doc->setRenderHint(Poppler::Document::OverprintPreview, true); // TODO what is this?
123 doc->setRenderHint(Poppler::Document::ThinLineSolid, true); // TODO what's the difference between ThinLineSolid and ThinLineShape?
124
125 page_count = doc->numPages();
126
127 min_aspect = numeric_limits<float>::max();
128 max_aspect = numeric_limits<float>::min();
129
130 k_page = new KPage[get_page_count()];
131 for (int i = 0; i < get_page_count(); i++) {
132 Poppler::Page *p = doc->page(i);
133 if (p == NULL) {
134 cerr << "failed to load page " << i << endl;
135 continue;
136 }
137 k_page[i].width = p->pageSizeF().width();
138 k_page[i].height = p->pageSizeF().height();
139
140 float aspect = k_page[i].width / k_page[i].height;
141 if (aspect < min_aspect) {
142 min_aspect = aspect;
143 }
144 if (aspect > max_aspect) {
145 max_aspect = aspect;
146 }
147
148 // k_page[i].label = p->label();
149 // if (k_page[i].label != QString::number(i + 1)) {
150 // cout << i << endl;
151 // }
152 delete p;
153 }
154 }
155
~ResourceManager()156 ResourceManager::~ResourceManager() {
157 shutdown();
158 }
159
shutdown()160 void ResourceManager::shutdown() {
161 if (worker != NULL) {
162 join_threads();
163 }
164 garbageMutex.lock();
165 for (int i = 0; i < 3; i++) {
166 garbage[i].clear();
167 }
168 garbageMutex.unlock();
169 requests.clear();
170 requestSemaphore.acquire(requestSemaphore.available());
171 #ifdef __linux__
172 ::close(inotify_fd);
173 delete i_notifier;
174 i_notifier = NULL;
175 #endif
176 delete doc;
177 delete[] k_page;
178 delete worker;
179 }
180
load(const QString & file,const QByteArray & password)181 void ResourceManager::load(const QString &file, const QByteArray &password) {
182 shutdown();
183 initialize(file, password);
184 }
185
is_valid() const186 bool ResourceManager::is_valid() const {
187 return (doc != NULL);
188 }
189
is_locked() const190 bool ResourceManager::is_locked() const {
191 if (doc == NULL) {
192 return false;
193 }
194 return doc->isLocked();
195 }
196
get_file() const197 const QString &ResourceManager::get_file() const {
198 return file;
199 }
200
set_file(const QString & new_file)201 void ResourceManager::set_file(const QString &new_file) {
202 file = new_file;
203 }
204
get_page(int page,int width,int index)205 const KPage *ResourceManager::get_page(int page, int width, int index) {
206 if (page < 0 || page >= get_page_count()) {
207 return NULL;
208 }
209
210 // page not available or wrong size/rotation/color
211 k_page[page].mutex.lock();
212 bool must_invert_colors = k_page[page].inverted_colors != inverted_colors;
213 if (must_invert_colors) {
214 k_page[page].toggle_invert_colors();
215 }
216
217 if (k_page[page].img[index].isNull() ||
218 k_page[page].status[index] != width ||
219 k_page[page].rotation[index] != rotation ||
220 must_invert_colors) {
221 enqueue(page, width, index);
222 }
223
224 return &k_page[page];
225 }
226
get_rotation() const227 int ResourceManager::get_rotation() const {
228 return rotation;
229 }
230
rotate(int value,bool relative)231 void ResourceManager::rotate(int value, bool relative) {
232 value %= 4;
233 if (relative) {
234 rotation = (rotation + value + 4) % 4;
235 } else {
236 rotation = value;
237 }
238 }
239
unlock_page(int page) const240 void ResourceManager::unlock_page(int page) const {
241 k_page[page].mutex.unlock();
242 }
243
invert_colors()244 void ResourceManager::invert_colors() {
245 inverted_colors = !inverted_colors;
246 }
247
are_colors_inverted() const248 bool ResourceManager::are_colors_inverted() const {
249 return inverted_colors;
250 }
251
collect_garbage(int keep_min,int keep_max,int index)252 void ResourceManager::collect_garbage(int keep_min, int keep_max, int index) {
253 requestMutex.lock();
254 if (index == 0) { // make a separate center_page for each index?
255 center_page = (keep_min + keep_max) / 2;
256 }
257 requestMutex.unlock();
258 // free distant pages
259 garbageMutex.lock();
260 for (set<int>::iterator it = garbage[index].begin(); it != garbage[index].end(); /* empty */) {
261 int page = *it;
262 if (page >= keep_min && page <= keep_max) {
263 ++it; // move on
264 continue;
265 }
266 garbage[index].erase(it++); // erase and move on (iterator becomes invalid)
267 #ifdef DEBUG
268 cerr << " removing page " << page << endl;
269 #endif
270 k_page[page].mutex.lock();
271 k_page[page].img[index] = QImage();
272 k_page[page].img_other[index] = QImage();
273 k_page[page].status[index] = 0;
274 k_page[page].rotation[index] = 0;
275 k_page[page].mutex.unlock();
276 }
277 garbageMutex.unlock();
278
279 // keep the request list small
280 if (keep_max < keep_min) {
281 return;
282 }
283 requestMutex.lock();
284 for (map<int,Request>::iterator it = requests.begin(); it != requests.end(); ) {
285 if ((it->first < keep_min || it->first > keep_max) && it->second.has_index(index)) {
286 if (!it->second.remove_index_ok(index)) { // no index left in request -> delete
287 requestSemaphore.acquire(1);
288 requests.erase(it++);
289 }
290 } else {
291 ++it;
292 }
293 }
294 requestMutex.unlock();
295 }
296
connect_canvas() const297 void ResourceManager::connect_canvas() const {
298 connect(worker, SIGNAL(page_rendered(int)), viewer->get_canvas(), SLOT(page_rendered(int)), Qt::UniqueConnection);
299 connect(worker, SIGNAL(page_rendered(int)), viewer->get_beamer(), SLOT(page_rendered(int)), Qt::UniqueConnection);
300 }
301
store_jump(int page)302 void ResourceManager::store_jump(int page) {
303 map<int,list<int>::iterator>::iterator it = jump_map.find(page);
304 if (it != jump_map.end()) {
305 jumplist.erase(it->second);
306 }
307 jumplist.push_back(page);
308 jump_map[page] = --jumplist.end();
309 cur_jump_pos = jumplist.end();
310
311 // cerr << "jumplist: ";
312 // for (list<int>::iterator it = jumplist.begin(); it != jumplist.end(); ++it) {
313 // if (it == cur_jump_pos) {
314 // cerr << "*";
315 // }
316 // cerr << *it << " ";
317 // }
318 // cerr << endl;
319 }
320
clear_jumps()321 void ResourceManager::clear_jumps() {
322 jumplist.clear();
323 jump_map.clear();
324 cur_jump_pos = jumplist.end();
325 }
326
jump_back()327 int ResourceManager::jump_back() {
328 if (cur_jump_pos == jumplist.begin()) {
329 return -1;
330 }
331 if (cur_jump_pos == jumplist.end()) {
332 store_jump(viewer->get_canvas()->get_layout()->get_page());
333 --cur_jump_pos;
334 }
335 --cur_jump_pos;
336 return *cur_jump_pos;
337 }
338
jump_forward()339 int ResourceManager::jump_forward() {
340 if (cur_jump_pos == jumplist.end() || cur_jump_pos == --jumplist.end()) {
341 return -1;
342 }
343 ++cur_jump_pos;
344 return *cur_jump_pos;
345 }
346
resolve_link_destination(const QString & name) const347 Poppler::LinkDestination *ResourceManager::resolve_link_destination(const QString &name) const {
348 return doc->linkDestination(name);
349 }
350
inotify_slot()351 void ResourceManager::inotify_slot() {
352 #ifdef __linux__
353 i_notifier->setEnabled(false);
354
355 size_t event_size = sizeof(struct inotify_event) + NAME_MAX + 1;
356 // take care of alignment
357 struct inotify_event i_buf[event_size / sizeof(struct inotify_event) + 1]; // has at least event_size
358 char *buf = reinterpret_cast<char *>(i_buf);
359
360 ssize_t bytes = read(inotify_fd, buf, sizeof(i_buf));
361 if (bytes == -1) {
362 cerr << "read: " << strerror(errno) << endl;
363 } else {
364 ssize_t offset = 0;
365 while (offset < bytes) {
366 struct inotify_event *event = reinterpret_cast<struct inotify_event *>(&buf[offset]);
367
368 QFileInfo info(file);
369 if (info.fileName() == QString::fromLocal8Bit(event->name)) {
370 viewer->reload(false); // don't clamp
371 i_notifier->setEnabled(true);
372 return;
373 }
374
375 offset += sizeof(struct inotify_event) + event->len;
376 }
377 }
378
379 i_notifier->setEnabled(true);
380 #endif
381 }
382
enqueue(int page,int width,int index)383 void ResourceManager::enqueue(int page, int width, int index) {
384 requestMutex.lock();
385 map<int,Request>::iterator it = requests.find(page);
386 if (it == requests.end()) {
387 requests.insert(make_pair(page, Request(width, index)));
388 requestSemaphore.release(1);
389 } else {
390 it->second.update(width, index);
391 }
392 requestMutex.unlock();
393 }
394
395 //QString ResourceManager::get_page_label(int page) const {
396 // if (page < 0 || page >= get_page_count()) {
397 // return QString();
398 // }
399 // return k_page[page].label;
400 //}
401
get_page_width(int page,bool rotated) const402 float ResourceManager::get_page_width(int page, bool rotated) const {
403 if (page < 0 || page >= get_page_count()) {
404 return -1;
405 }
406 if (!rotated || rotation == 0 || rotation == 2) {
407 return k_page[page].width;
408 }
409 // swap if rotated by 90 or 270 degrees
410 return k_page[page].height;
411 }
412
get_page_height(int page,bool rotated) const413 float ResourceManager::get_page_height(int page, bool rotated) const {
414 if (page < 0 || page >= get_page_count()) {
415 return -1;
416 }
417 if (!rotated || rotation == 0 || rotation == 2) {
418 return k_page[page].height;
419 }
420 return k_page[page].width;
421 }
422
get_page_aspect(int page,bool rotated) const423 float ResourceManager::get_page_aspect(int page, bool rotated) const {
424 if (page < 0 || page >= get_page_count()) {
425 return -1;
426 }
427 if (!rotated || rotation == 0 || rotation == 2) {
428 return k_page[page].width / k_page[page].height;
429 }
430 return k_page[page].height / k_page[page].width;
431 }
432
get_min_aspect(bool rotated) const433 float ResourceManager::get_min_aspect(bool rotated) const {
434 if (!rotated || rotation == 0 || rotation == 2) {
435 return min_aspect;
436 } else {
437 return 1.0f / max_aspect;
438 }
439 }
440
get_max_aspect(bool rotated) const441 float ResourceManager::get_max_aspect(bool rotated) const {
442 if (!rotated || rotation == 0 || rotation == 2) {
443 return max_aspect;
444 } else {
445 return 1.0f / min_aspect;
446 }
447 }
448
get_page_count() const449 int ResourceManager::get_page_count() const {
450 return page_count;
451 }
452
get_links(int page)453 const QList<Poppler::Link *> *ResourceManager::get_links(int page) {
454 if (page < 0 || page >= get_page_count()) {
455 return NULL;
456 }
457 link_mutex.lock();
458 QList<Poppler::Link *> *l = k_page[page].links;
459 link_mutex.unlock();
460 return l;
461 }
462
get_text(int page)463 const QList<SelectionLine *> *ResourceManager::get_text(int page) {
464 if (page < 0 || page >= get_page_count()) {
465 return NULL;
466 }
467 link_mutex.lock();
468 QList<SelectionLine *> *t = k_page[page].text;
469 link_mutex.unlock();
470 return t;
471 }
472
get_toc() const473 QDomDocument *ResourceManager::get_toc() const {
474 if (doc == NULL || doc->isLocked()) {
475 return NULL;
476 }
477 return doc->toc();
478 }
479
join_threads()480 void ResourceManager::join_threads() {
481 worker->die = true;
482 requestSemaphore.release(1);
483 worker->wait();
484 }
485
486