2 * Copyright 2010-2011, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
7 #include "ImageCache.h"
13 #include <BitmapStream.h>
16 #include <Messenger.h>
17 #include <TranslatorRoster.h>
19 #include <AutoDeleter.h>
21 #include "ShowImageConstants.h"
27 # define TRACE(x, ...) printf(x, __VA_ARGS__)
29 # define TRACE(x, ...) ;
37 std::set
<BMessenger
> listeners
;
44 BitmapOwner::BitmapOwner(BBitmap
* bitmap
)
51 BitmapOwner::~BitmapOwner()
60 ImageCache::ImageCache()
62 fLocker("image cache"),
67 get_system_info(&info
);
69 fMaxThreadCount
= info
.cpu_count
- 1;
70 if (fMaxThreadCount
< 1)
72 fMaxBytes
= info
.max_pages
* B_PAGE_SIZE
/ 5;
74 TRACE("max thread count: %" B_PRId32
", max bytes: %" B_PRIu64
75 ", max entries: %" B_PRIuSIZE
"\n",
76 fMaxThreadCount
, fMaxBytes
, fMaxEntries
);
80 ImageCache::~ImageCache()
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
);
106 QueueMap::iterator findQueue
= fQueueMap
.find(std::make_pair(ref
, page
));
109 if (findQueue
== fQueueMap
.end()) {
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
);
120 TRACE("add to queue %s\n", ref
.name
);
122 // Push new entry to the queue
123 entry
= new(std::nothrow
) QueueEntry();
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) {
143 fQueueMap
.insert(std::make_pair(
144 std::make_pair(entry
->ref
, entry
->page
), entry
));
145 fQueue
.push_front(entry
);
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
);
163 // empty the working queue
165 while (!fQueue
.empty()) {
166 QueueEntry
* entry
= *fQueue
.begin();
172 // wait for running thread
175 thread
= find_thread("image loader");
178 wait_for_thread(thread
, NULL
);
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
191 self
->fLocker
.Lock();
192 if (self
->fQueue
.empty()) {
193 self
->fLocker
.Unlock();
197 QueueEntry
* entry
= *self
->fQueue
.begin();
198 TRACE("%ld: got entry %s from queue.\n", find_thread(NULL
),
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();
214 atomic_add(&self
->fThreadCount
, -1);
215 TRACE("%ld: end worker thread\n", find_thread(NULL
));
221 ImageCache::_RetrieveImage(QueueEntry
* queueEntry
, CacheEntry
** _entry
)
223 CacheEntry
* entry
= new(std::nothrow
) CacheEntry();
227 ObjectDeleter
<CacheEntry
> deleter(entry
);
229 BTranslatorRoster
* roster
= BTranslatorRoster::Default();
234 status_t status
= file
.SetTo(&queueEntry
->ref
, B_READ_ONLY
);
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
)
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
);
269 if (outstream
.DetachBitmap(&bitmap
) != B_OK
)
272 entry
->bitmapOwner
= new(std::nothrow
) BitmapOwner(bitmap
);
273 if (entry
->bitmapOwner
== NULL
) {
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
;
290 entry
->pageCount
= 1;
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)
310 // Remove the oldest entry
311 entry
= fCacheEntriesByAge
.RemoveHead();
312 TRACE("%ld: purge cached entry %s from queue.\n", find_thread(NULL
),
314 fBytes
-= entry
->bitmap
->BitsLength();
315 fCacheMap
.erase(std::make_pair(entry
->ref
, entry
->page
));
317 entry
->bitmapOwner
->ReleaseReference();
326 ImageCache::_NotifyListeners(CacheEntry
* entry
, QueueEntry
* queueEntry
)
328 ASSERT(fLocker
.IsLocked());
330 if (queueEntry
->listeners
.empty())
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(¬ification
) == B_OK
&& entry
!= NULL
) {
342 entry
->bitmapOwner
->AcquireReference();
343 // this is the reference owned by the target
350 ImageCache::_NotifyTarget(CacheEntry
* entry
, const BMessenger
* target
)
355 BMessage
notification(kMsgImageCacheImageLoaded
);
356 _BuildNotification(entry
, notification
);
358 if (target
->SendMessage(¬ification
) == B_OK
&& entry
!= NULL
) {
359 entry
->bitmapOwner
->AcquireReference();
360 // this is the reference owned by the target
366 ImageCache::_BuildNotification(CacheEntry
* entry
, BMessage
& message
)
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
);