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