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