vfs: check userland buffers before reading them.
[haiku.git] / src / apps / showimage / ImageCache.cpp
blob25e0547b1e97d4dac261a870412853a92ee4b29b
1 /*
2 * Copyright 2010-2011, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "ImageCache.h"
9 #include <new>
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>
19 #include <AutoDeleter.h>
21 #include "ShowImageConstants.h"
24 //#define TRACE_CACHE
25 #undef TRACE
26 #ifdef TRACE_CACHE
27 # define TRACE(x, ...) printf(x, __VA_ARGS__)
28 #else
29 # define TRACE(x, ...) ;
30 #endif
33 struct QueueEntry {
34 entry_ref ref;
35 int32 page;
36 status_t status;
37 std::set<BMessenger> listeners;
41 // #pragma mark -
44 BitmapOwner::BitmapOwner(BBitmap* bitmap)
46 fBitmap(bitmap)
51 BitmapOwner::~BitmapOwner()
53 delete fBitmap;
57 // #pragma mark -
60 ImageCache::ImageCache()
62 fLocker("image cache"),
63 fThreadCount(0),
64 fBytes(0)
66 system_info info;
67 get_system_info(&info);
69 fMaxThreadCount = info.cpu_count - 1;
70 if (fMaxThreadCount < 1)
71 fMaxThreadCount = 1;
72 fMaxBytes = info.max_pages * B_PAGE_SIZE / 5;
73 fMaxEntries = 10;
74 TRACE("max thread count: %" B_PRId32 ", max bytes: %" B_PRIu64
75 ", max entries: %" B_PRIuSIZE "\n",
76 fMaxThreadCount, fMaxBytes, fMaxEntries);
80 ImageCache::~ImageCache()
82 Stop();
86 status_t
87 ImageCache::RetrieveImage(const entry_ref& ref, int32 page,
88 const BMessenger* target)
90 BAutolock locker(fLocker);
92 CacheMap::iterator find = fCacheMap.find(std::make_pair(ref, page));
93 if (find != fCacheMap.end()) {
94 CacheEntry* entry = find->second;
96 // Requeue cache entry to the end of the by-age list
97 TRACE("requeue trace entry %s\n", ref.name);
98 fCacheEntriesByAge.Remove(entry);
99 fCacheEntriesByAge.Add(entry);
101 // Notify target, if any
102 _NotifyTarget(entry, target);
103 return B_OK;
106 QueueMap::iterator findQueue = fQueueMap.find(std::make_pair(ref, page));
107 QueueEntry* entry;
109 if (findQueue == fQueueMap.end()) {
110 if (target == NULL
111 && ((fCacheMap.size() < 4 && fCacheMap.size() > 1
112 && fBytes + fBytes / fCacheMap.size() > fMaxBytes)
113 || (fMaxThreadCount == 1 && fQueueMap.size() > 1))) {
114 // Don't accept any further precaching if we're low on memory
115 // anyway, or if there is already a busy queue.
116 TRACE("ignore entry %s\n", ref.name);
117 return B_NO_MEMORY;
120 TRACE("add to queue %s\n", ref.name);
122 // Push new entry to the queue
123 entry = new(std::nothrow) QueueEntry();
124 if (entry == NULL)
125 return B_NO_MEMORY;
127 entry->ref = ref;
128 entry->page = page;
130 if (fThreadCount < fMaxThreadCount) {
131 // start a new worker thread to load the image
132 thread_id thread = spawn_thread(&ImageCache::_QueueWorkerThread,
133 "image loader", B_LOW_PRIORITY, this);
134 if (thread >= B_OK) {
135 atomic_add(&fThreadCount, 1);
136 resume_thread(thread);
137 } else if (fThreadCount == 0) {
138 delete entry;
139 return thread;
143 fQueueMap.insert(std::make_pair(
144 std::make_pair(entry->ref, entry->page), entry));
145 fQueue.push_front(entry);
146 } else {
147 entry = findQueue->second;
148 TRACE("got entry %s from cache\n", entry->ref.name);
151 if (target != NULL) {
152 // Attach target as listener
153 entry->listeners.insert(*target);
156 return B_OK;
160 void
161 ImageCache::Stop()
163 // empty the working queue
164 fLocker.Lock();
165 while (!fQueue.empty()) {
166 QueueEntry* entry = *fQueue.begin();
167 fQueue.pop_front();
168 delete entry;
170 fLocker.Unlock();
172 // wait for running thread
173 thread_id thread;
174 while (true) {
175 thread = find_thread("image loader");
176 if (thread < 0)
177 break;
178 wait_for_thread(thread, NULL);
183 /*static*/ status_t
184 ImageCache::_QueueWorkerThread(void* _self)
186 ImageCache* self = (ImageCache*)_self;
187 TRACE("%ld: start worker thread\n", find_thread(NULL));
189 // get next queue entry
190 while (true) {
191 self->fLocker.Lock();
192 if (self->fQueue.empty()) {
193 self->fLocker.Unlock();
194 break;
197 QueueEntry* entry = *self->fQueue.begin();
198 TRACE("%ld: got entry %s from queue.\n", find_thread(NULL),
199 entry->ref.name);
200 self->fQueue.pop_front();
201 self->fLocker.Unlock();
203 CacheEntry* cacheEntry = NULL;
204 entry->status = self->_RetrieveImage(entry, &cacheEntry);
206 self->fLocker.Lock();
207 self->fQueueMap.erase(std::make_pair(entry->ref, entry->page));
208 self->_NotifyListeners(cacheEntry, entry);
209 self->fLocker.Unlock();
211 delete entry;
214 atomic_add(&self->fThreadCount, -1);
215 TRACE("%ld: end worker thread\n", find_thread(NULL));
216 return B_OK;
220 status_t
221 ImageCache::_RetrieveImage(QueueEntry* queueEntry, CacheEntry** _entry)
223 CacheEntry* entry = new(std::nothrow) CacheEntry();
224 if (entry == NULL)
225 return B_NO_MEMORY;
227 ObjectDeleter<CacheEntry> deleter(entry);
229 BTranslatorRoster* roster = BTranslatorRoster::Default();
230 if (roster == NULL)
231 return B_ERROR;
233 BFile file;
234 status_t status = file.SetTo(&queueEntry->ref, B_READ_ONLY);
235 if (status != B_OK)
236 return status;
238 translator_info info;
239 memset(&info, 0, sizeof(translator_info));
240 BMessage ioExtension;
242 if (queueEntry->page != 0
243 && ioExtension.AddInt32("/documentIndex", queueEntry->page) != B_OK)
244 return B_NO_MEMORY;
246 // TODO: this doesn't work for images that already are in the queue...
247 if (!queueEntry->listeners.empty()) {
248 BMessage progress(kMsgImageCacheProgressUpdate);
249 progress.AddRef("ref", &queueEntry->ref);
250 ioExtension.AddMessenger("/progressMonitor",
251 *queueEntry->listeners.begin());
252 ioExtension.AddMessage("/progressMessage", &progress);
255 // Translate image data and create a new ShowImage window
257 BBitmapStream outstream;
259 status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
260 B_TRANSLATOR_BITMAP);
261 if (status == B_OK) {
262 status = roster->Translate(&file, &info, &ioExtension, &outstream,
263 B_TRANSLATOR_BITMAP);
265 if (status != B_OK)
266 return status;
268 BBitmap* bitmap;
269 if (outstream.DetachBitmap(&bitmap) != B_OK)
270 return B_ERROR;
272 entry->bitmapOwner = new(std::nothrow) BitmapOwner(bitmap);
273 if (entry->bitmapOwner == NULL) {
274 delete bitmap;
275 return B_NO_MEMORY;
278 entry->ref = queueEntry->ref;
279 entry->page = queueEntry->page;
280 entry->bitmap = bitmap;
281 entry->type = info.name;
282 entry->mimeType = info.MIME;
284 // get the number of documents (pages) if it has been supplied
285 int32 documentCount = 0;
286 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
287 && documentCount > 0)
288 entry->pageCount = documentCount;
289 else
290 entry->pageCount = 1;
292 deleter.Detach();
293 *_entry = entry;
295 BAutolock locker(fLocker);
297 fCacheMap.insert(std::make_pair(
298 std::make_pair(entry->ref, entry->page), entry));
299 fCacheEntriesByAge.Add(entry);
301 fBytes += bitmap->BitsLength();
303 TRACE("%ld: cached entry %s from queue (%" B_PRIu64 " bytes.\n",
304 find_thread(NULL), entry->ref.name, fBytes);
306 while (fBytes > fMaxBytes || fCacheMap.size() > fMaxEntries) {
307 if (fCacheMap.size() <= 2)
308 break;
310 // Remove the oldest entry
311 entry = fCacheEntriesByAge.RemoveHead();
312 TRACE("%ld: purge cached entry %s from queue.\n", find_thread(NULL),
313 entry->ref.name);
314 fBytes -= entry->bitmap->BitsLength();
315 fCacheMap.erase(std::make_pair(entry->ref, entry->page));
317 entry->bitmapOwner->ReleaseReference();
318 delete entry;
321 return B_OK;
325 void
326 ImageCache::_NotifyListeners(CacheEntry* entry, QueueEntry* queueEntry)
328 ASSERT(fLocker.IsLocked());
330 if (queueEntry->listeners.empty())
331 return;
333 BMessage notification(kMsgImageCacheImageLoaded);
334 _BuildNotification(entry, notification);
336 if (queueEntry->status != B_OK)
337 notification.AddInt32("error", queueEntry->status);
339 std::set<BMessenger>::iterator iterator = queueEntry->listeners.begin();
340 for (; iterator != queueEntry->listeners.end(); iterator++) {
341 if (iterator->SendMessage(&notification) == B_OK && entry != NULL) {
342 entry->bitmapOwner->AcquireReference();
343 // this is the reference owned by the target
349 void
350 ImageCache::_NotifyTarget(CacheEntry* entry, const BMessenger* target)
352 if (target == NULL)
353 return;
355 BMessage notification(kMsgImageCacheImageLoaded);
356 _BuildNotification(entry, notification);
358 if (target->SendMessage(&notification) == B_OK && entry != NULL) {
359 entry->bitmapOwner->AcquireReference();
360 // this is the reference owned by the target
365 void
366 ImageCache::_BuildNotification(CacheEntry* entry, BMessage& message)
368 if (entry == NULL)
369 return;
371 message.AddString("type", entry->type);
372 message.AddString("mime", entry->mimeType);
373 message.AddRef("ref", &entry->ref);
374 message.AddInt32("page", entry->page);
375 message.AddInt32("pageCount", entry->pageCount);
376 message.AddPointer("bitmap", (void*)entry->bitmap);
377 message.AddPointer("bitmapOwner", (void*)entry->bitmapOwner);