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 44 BitmapOwner::BitmapOwner(BBitmap* bitmap) 45 : 46 fBitmap(bitmap) 47 { 48 } 49 50 51 BitmapOwner::~BitmapOwner() 52 { 53 delete fBitmap; 54 } 55 56 57 // #pragma mark - 58 59 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 80 ImageCache::~ImageCache() 81 { 82 Stop(); 83 } 84 85 86 status_t 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 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 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 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 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 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 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