xref: /haiku/src/apps/showimage/ImageCache.cpp (revision a906d0a031e721e2f2ec9d95274103e74a3a774f)
1 /*
2  * Copyright 2010, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "ImageCache.h"
8 
9 #include <new>
10 
11 #include <Autolock.h>
12 #include <Bitmap.h>
13 #include <BitmapStream.h>
14 #include <Debug.h>
15 #include <File.h>
16 #include <Messenger.h>
17 #include <TranslatorRoster.h>
18 
19 #include <AutoDeleter.h>
20 
21 #include "ShowImageConstants.h"
22 
23 
24 struct QueueEntry {
25 	entry_ref				ref;
26 	int32					page;
27 	status_t				status;
28 	std::set<BMessenger>	listeners;
29 };
30 
31 
32 /*static*/ ImageCache ImageCache::sCache;
33 
34 
35 // #pragma mark -
36 
37 
38 BitmapOwner::BitmapOwner(BBitmap* bitmap)
39 	:
40 	fBitmap(bitmap)
41 {
42 }
43 
44 
45 BitmapOwner::~BitmapOwner()
46 {
47 	delete fBitmap;
48 }
49 
50 
51 // #pragma mark -
52 
53 
54 ImageCache::ImageCache()
55 	:
56 	fLocker("image cache"),
57 	fThreadCount(0),
58 	fBytes(0)
59 {
60 	system_info info;
61 	get_system_info(&info);
62 
63 	fMaxThreadCount = (info.cpu_count + 1) / 2;
64 	fMaxBytes = info.max_pages * B_PAGE_SIZE / 8;
65 	fMaxEntries = 10;
66 }
67 
68 
69 ImageCache::~ImageCache()
70 {
71 	// TODO: delete CacheEntries, and QueueEntries
72 }
73 
74 
75 status_t
76 ImageCache::RetrieveImage(const entry_ref& ref, int32 page,
77 	const BMessenger* target)
78 {
79 	BAutolock locker(fLocker);
80 
81 	CacheMap::iterator find = fCacheMap.find(std::make_pair(ref, page));
82 	if (find != fCacheMap.end()) {
83 		CacheEntry* entry = find->second;
84 
85 		// Requeue cache entry to the end of the by-age list
86 		fCacheEntriesByAge.Remove(entry);
87 		fCacheEntriesByAge.Add(entry);
88 
89 		// Notify target, if any
90 		_NotifyTarget(entry, target);
91 		return B_OK;
92 	}
93 
94 	QueueMap::iterator findQueue = fQueueMap.find(std::make_pair(ref, page));
95 	QueueEntry* entry;
96 
97 	if (findQueue == fQueueMap.end()) {
98 		if (target == NULL
99 			&& ((fCacheMap.size() < 5 && fBytes > fMaxBytes * 1 / 2)
100 				|| (fMaxThreadCount == 1 && fQueueMap.size() > 1))) {
101 			// Don't accept any further precaching if we're low on memory
102 			// anyway, or if there is already a busy queue.
103 			return B_NO_MEMORY;
104 		}
105 
106 		// Push new entry to the queue
107 		entry = new(std::nothrow) QueueEntry();
108 		if (entry == NULL)
109 			return B_NO_MEMORY;
110 
111 		entry->ref = ref;
112 		entry->page = page;
113 
114 		if (fThreadCount < fMaxThreadCount) {
115 			// start a new worker thread to load the image
116 			thread_id thread = spawn_thread(&ImageCache::_QueueWorkerThread,
117 				"image loader", B_LOW_PRIORITY, this);
118 			if (thread >= B_OK) {
119 				atomic_add(&fThreadCount, 1);
120 				resume_thread(thread);
121 			} else if (fThreadCount == 0) {
122 				delete entry;
123 				return thread;
124 			}
125 		}
126 
127 		fQueueMap.insert(std::make_pair(
128 			std::make_pair(entry->ref, entry->page), entry));
129 		fQueue.push_front(entry);
130 	} else
131 		entry = findQueue->second;
132 
133 	if (target != NULL) {
134 		// Attach target as listener
135 		entry->listeners.insert(*target);
136 	}
137 
138 	return B_OK;
139 }
140 
141 
142 /*static*/ status_t
143 ImageCache::_QueueWorkerThread(void* _self)
144 {
145 	ImageCache* self = (ImageCache*)_self;
146 
147 	// get next queue entry
148 	while (true) {
149 		self->fLocker.Lock();
150 		if (self->fQueue.empty()) {
151 			self->fLocker.Unlock();
152 			break;
153 		}
154 
155 		QueueEntry* entry = *self->fQueue.begin();
156 		self->fQueue.pop_front();
157 		self->fLocker.Unlock();
158 
159 		if (entry == NULL)
160 			break;
161 
162 		CacheEntry* cacheEntry = NULL;
163 		entry->status = self->_RetrieveImage(entry, &cacheEntry);
164 
165 		self->fLocker.Lock();
166 		self->fQueueMap.erase(std::make_pair(entry->ref, entry->page));
167 		self->fLocker.Unlock();
168 
169 		self->_NotifyListeners(cacheEntry, entry);
170 		delete entry;
171 	}
172 
173 	atomic_add(&self->fThreadCount, -1);
174 	return B_OK;
175 }
176 
177 
178 status_t
179 ImageCache::_RetrieveImage(QueueEntry* queueEntry, CacheEntry** _entry)
180 {
181 	CacheEntry* entry = new(std::nothrow) CacheEntry();
182 	if (entry == NULL)
183 		return B_NO_MEMORY;
184 
185 	ObjectDeleter<CacheEntry> deleter(entry);
186 
187 	BTranslatorRoster* roster = BTranslatorRoster::Default();
188 	if (roster == NULL)
189 		return B_ERROR;
190 
191 	BFile file;
192 	status_t status = file.SetTo(&queueEntry->ref, B_READ_ONLY);
193 	if (status != B_OK)
194 		return status;
195 
196 	translator_info info;
197 	memset(&info, 0, sizeof(translator_info));
198 	BMessage ioExtension;
199 
200 	if (queueEntry->page != 0
201 		&& ioExtension.AddInt32("/documentIndex", queueEntry->page) != B_OK)
202 		return B_NO_MEMORY;
203 
204 	// TODO: this doesn't work for images that already are in the queue...
205 	if (!queueEntry->listeners.empty()) {
206 		BMessage progress(kMsgImageCacheProgressUpdate);
207 		progress.AddRef("ref", &queueEntry->ref);
208 		ioExtension.AddMessenger("/progressMonitor",
209 			*queueEntry->listeners.begin());
210 		ioExtension.AddMessage("/progressMessage", &progress);
211 	}
212 
213 	// Translate image data and create a new ShowImage window
214 
215 	BBitmapStream outstream;
216 
217 	status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
218 		B_TRANSLATOR_BITMAP);
219 	if (status == B_OK) {
220 		status = roster->Translate(&file, &info, &ioExtension, &outstream,
221 			B_TRANSLATOR_BITMAP);
222 	}
223 	if (status != B_OK)
224 		return status;
225 
226 	BBitmap* bitmap;
227 	if (outstream.DetachBitmap(&bitmap) != B_OK)
228 		return B_ERROR;
229 
230 	entry->bitmapOwner = new(std::nothrow) BitmapOwner(bitmap);
231 	if (entry->bitmapOwner == NULL) {
232 		delete bitmap;
233 		return B_NO_MEMORY;
234 	}
235 
236 	entry->ref = queueEntry->ref;
237 	entry->page = queueEntry->page;
238 	entry->bitmap = bitmap;
239 	entry->type = info.name;
240 	entry->mimeType = info.MIME;
241 
242 	// get the number of documents (pages) if it has been supplied
243 	int32 documentCount = 0;
244 	if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
245 		&& documentCount > 0)
246 		entry->pageCount = documentCount;
247 	else
248 		entry->pageCount = 1;
249 
250 	deleter.Detach();
251 	*_entry = entry;
252 
253 	BAutolock locker(fLocker);
254 
255 	fCacheMap.insert(std::make_pair(
256 		std::make_pair(entry->ref, entry->page), entry));
257 	fCacheEntriesByAge.Add(entry);
258 
259 	fBytes += bitmap->BitsLength();
260 
261 	while (fBytes > fMaxBytes || fCacheMap.size() > fMaxEntries) {
262 		if (fCacheMap.size() <= 2)
263 			break;
264 
265 		// Remove the oldest entry
266 		entry = fCacheEntriesByAge.RemoveHead();
267 		fBytes -= entry->bitmap->BitsLength();
268 		fCacheMap.erase(std::make_pair(entry->ref, entry->page));
269 
270 		entry->bitmapOwner->ReleaseReference();
271 		delete entry;
272 	}
273 
274 	return B_OK;
275 }
276 
277 
278 void
279 ImageCache::_NotifyListeners(CacheEntry* entry, QueueEntry* queueEntry)
280 {
281 	ASSERT(fLocker.IsLocked());
282 
283 	if (queueEntry->listeners.empty())
284 		return;
285 
286 	BMessage notification(kMsgImageCacheImageLoaded);
287 	_BuildNotification(entry, notification);
288 
289 	if (queueEntry->status != B_OK)
290 		notification.AddInt32("error", queueEntry->status);
291 
292 	std::set<BMessenger>::iterator iterator = queueEntry->listeners.begin();
293 	for (; iterator != queueEntry->listeners.end(); iterator++) {
294 		if (iterator->SendMessage(&notification) == B_OK) {
295 			entry->bitmapOwner->AcquireReference();
296 				// this is the reference owned by the target
297 		}
298 	}
299 }
300 
301 
302 void
303 ImageCache::_NotifyTarget(CacheEntry* entry, const BMessenger* target)
304 {
305 	if (target == NULL)
306 		return;
307 
308 	BMessage notification(kMsgImageCacheImageLoaded);
309 	_BuildNotification(entry, notification);
310 
311 	if (target->SendMessage(&notification) == B_OK) {
312 		entry->bitmapOwner->AcquireReference();
313 			// this is the reference owned by the target
314 	}
315 }
316 
317 
318 void
319 ImageCache::_BuildNotification(CacheEntry* entry, BMessage& message)
320 {
321 	if (entry == NULL)
322 		return;
323 
324 	message.AddString("type", entry->type);
325 	message.AddString("mime", entry->mimeType);
326 	message.AddRef("ref", &entry->ref);
327 	message.AddInt32("page", entry->page);
328 	message.AddInt32("pageCount", entry->pageCount);
329 	message.AddPointer("bitmap", (void*)entry->bitmap);
330 	message.AddPointer("bitmapOwner", (void*)entry->bitmapOwner);
331 }
332