1 /*
2 * Copyright 2010-2011, 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 //#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
31
32
33 struct QueueEntry {
34 entry_ref ref;
35 int32 page;
36 status_t status;
37 std::set<BMessenger> listeners;
38 };
39
40
41 // #pragma mark -
42
43
BitmapOwner(BBitmap * bitmap)44 BitmapOwner::BitmapOwner(BBitmap* bitmap)
45 :
46 fBitmap(bitmap)
47 {
48 }
49
50
~BitmapOwner()51 BitmapOwner::~BitmapOwner()
52 {
53 delete fBitmap;
54 }
55
56
57 // #pragma mark -
58
59
ImageCache()60 ImageCache::ImageCache()
61 :
62 fLocker("image cache"),
63 fThreadCount(0),
64 fBytes(0)
65 {
66 system_info info;
67 get_system_info(&info);
68
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);
77 }
78
79
~ImageCache()80 ImageCache::~ImageCache()
81 {
82 Stop();
83 }
84
85
86 status_t
RetrieveImage(const entry_ref & ref,int32 page,const BMessenger * target)87 ImageCache::RetrieveImage(const entry_ref& ref, int32 page,
88 const BMessenger* target)
89 {
90 BAutolock locker(fLocker);
91
92 CacheMap::iterator find = fCacheMap.find(std::make_pair(ref, page));
93 if (find != fCacheMap.end()) {
94 CacheEntry* entry = find->second;
95
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);
100
101 // Notify target, if any
102 _NotifyTarget(entry, target);
103 return B_OK;
104 }
105
106 QueueMap::iterator findQueue = fQueueMap.find(std::make_pair(ref, page));
107 QueueEntry* entry;
108
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;
118 }
119
120 TRACE("add to queue %s\n", ref.name);
121
122 // Push new entry to the queue
123 entry = new(std::nothrow) QueueEntry();
124 if (entry == NULL)
125 return B_NO_MEMORY;
126
127 entry->ref = ref;
128 entry->page = page;
129
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;
140 }
141 }
142
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);
149 }
150
151 if (target != NULL) {
152 // Attach target as listener
153 entry->listeners.insert(*target);
154 }
155
156 return B_OK;
157 }
158
159
160 void
Stop()161 ImageCache::Stop()
162 {
163 // empty the working queue
164 fLocker.Lock();
165 while (!fQueue.empty()) {
166 QueueEntry* entry = *fQueue.begin();
167 fQueue.pop_front();
168 delete entry;
169 }
170 fLocker.Unlock();
171
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);
179 }
180 }
181
182
183 /*static*/ status_t
_QueueWorkerThread(void * _self)184 ImageCache::_QueueWorkerThread(void* _self)
185 {
186 ImageCache* self = (ImageCache*)_self;
187 TRACE("%ld: start worker thread\n", find_thread(NULL));
188
189 // get next queue entry
190 while (true) {
191 self->fLocker.Lock();
192 if (self->fQueue.empty()) {
193 self->fLocker.Unlock();
194 break;
195 }
196
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();
202
203 CacheEntry* cacheEntry = NULL;
204 entry->status = self->_RetrieveImage(entry, &cacheEntry);
205
206 self->fLocker.Lock();
207 self->fQueueMap.erase(std::make_pair(entry->ref, entry->page));
208 self->_NotifyListeners(cacheEntry, entry);
209 self->fLocker.Unlock();
210
211 delete entry;
212 }
213
214 atomic_add(&self->fThreadCount, -1);
215 TRACE("%ld: end worker thread\n", find_thread(NULL));
216 return B_OK;
217 }
218
219
220 status_t
_RetrieveImage(QueueEntry * queueEntry,CacheEntry ** _entry)221 ImageCache::_RetrieveImage(QueueEntry* queueEntry, CacheEntry** _entry)
222 {
223 CacheEntry* entry = new(std::nothrow) CacheEntry();
224 if (entry == NULL)
225 return B_NO_MEMORY;
226
227 ObjectDeleter<CacheEntry> deleter(entry);
228
229 BTranslatorRoster* roster = BTranslatorRoster::Default();
230 if (roster == NULL)
231 return B_ERROR;
232
233 BFile file;
234 status_t status = file.SetTo(&queueEntry->ref, B_READ_ONLY);
235 if (status != B_OK)
236 return status;
237
238 translator_info info;
239 memset(&info, 0, sizeof(translator_info));
240 BMessage ioExtension;
241
242 if (queueEntry->page != 0
243 && ioExtension.AddInt32("/documentIndex", queueEntry->page) != B_OK)
244 return B_NO_MEMORY;
245
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);
253 }
254
255 // Translate image data and create a new ShowImage window
256
257 BBitmapStream outstream;
258
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);
264 }
265 if (status != B_OK)
266 return status;
267
268 BBitmap* bitmap;
269 if (outstream.DetachBitmap(&bitmap) != B_OK)
270 return B_ERROR;
271
272 entry->bitmapOwner = new(std::nothrow) BitmapOwner(bitmap);
273 if (entry->bitmapOwner == NULL) {
274 delete bitmap;
275 return B_NO_MEMORY;
276 }
277
278 entry->ref = queueEntry->ref;
279 entry->page = queueEntry->page;
280 entry->bitmap = bitmap;
281 entry->type = info.name;
282 entry->mimeType = info.MIME;
283
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;
291
292 deleter.Detach();
293 *_entry = entry;
294
295 BAutolock locker(fLocker);
296
297 fCacheMap.insert(std::make_pair(
298 std::make_pair(entry->ref, entry->page), entry));
299 fCacheEntriesByAge.Add(entry);
300
301 fBytes += bitmap->BitsLength();
302
303 TRACE("%ld: cached entry %s from queue (%" B_PRIu64 " bytes.\n",
304 find_thread(NULL), entry->ref.name, fBytes);
305
306 while (fBytes > fMaxBytes || fCacheMap.size() > fMaxEntries) {
307 if (fCacheMap.size() <= 2)
308 break;
309
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));
316
317 entry->bitmapOwner->ReleaseReference();
318 delete entry;
319 }
320
321 return B_OK;
322 }
323
324
325 void
_NotifyListeners(CacheEntry * entry,QueueEntry * queueEntry)326 ImageCache::_NotifyListeners(CacheEntry* entry, QueueEntry* queueEntry)
327 {
328 ASSERT(fLocker.IsLocked());
329
330 if (queueEntry->listeners.empty())
331 return;
332
333 BMessage notification(kMsgImageCacheImageLoaded);
334 _BuildNotification(entry, notification);
335
336 if (queueEntry->status != B_OK)
337 notification.AddInt32("error", queueEntry->status);
338
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
344 }
345 }
346 }
347
348
349 void
_NotifyTarget(CacheEntry * entry,const BMessenger * target)350 ImageCache::_NotifyTarget(CacheEntry* entry, const BMessenger* target)
351 {
352 if (target == NULL)
353 return;
354
355 BMessage notification(kMsgImageCacheImageLoaded);
356 _BuildNotification(entry, notification);
357
358 if (target->SendMessage(¬ification) == B_OK && entry != NULL) {
359 entry->bitmapOwner->AcquireReference();
360 // this is the reference owned by the target
361 }
362 }
363
364
365 void
_BuildNotification(CacheEntry * entry,BMessage & message)366 ImageCache::_BuildNotification(CacheEntry* entry, BMessage& message)
367 {
368 if (entry == NULL)
369 return;
370
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);
378 }
379