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