1 /* 2 * Copyright 2003-2010, Haiku, Inc. All Rights Reserved. 3 * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd. 4 * Copyright 2006 Bernd Korz. All Rights Reserved 5 * Distributed under the terms of the MIT License. 6 * 7 * Authors: 8 * Fernando Francisco de Oliveira 9 * Michael Wilber 10 * Michael Pfeiffer 11 * Ryan Leavengood 12 * yellowTAB GmbH 13 * Bernd Korz 14 * Stephan Aßmus <superstippi@gmx.de> 15 * Axel Dörfler, axeld@pinc-software.de 16 */ 17 18 19 #include "ImageFileNavigator.h" 20 21 #include <deque> 22 #include <map> 23 #include <new> 24 #include <set> 25 26 #include <stdio.h> 27 28 #include <Bitmap.h> 29 #include <BitmapStream.h> 30 #include <Directory.h> 31 #include <Entry.h> 32 #include <File.h> 33 #include <Locker.h> 34 #include <ObjectList.h> 35 #include <Path.h> 36 #include <TranslatorRoster.h> 37 38 #include <AutoDeleter.h> 39 #include <tracker_private.h> 40 #include <kernel/util/DoublyLinkedList.h> 41 42 #include "ProgressWindow.h" 43 #include "ShowImageConstants.h" 44 45 46 class Navigator { 47 public: 48 Navigator(); 49 virtual ~Navigator(); 50 51 virtual bool FindNextImage(const entry_ref& currentRef, 52 entry_ref& ref, bool next, bool rewind) = 0; 53 virtual void UpdateSelection(const entry_ref& ref) = 0; 54 55 protected: 56 bool IsImage(const entry_ref& ref); 57 }; 58 59 60 // Navigation to the next/previous image file is based on 61 // communication with Tracker, the folder containing the current 62 // image needs to be open for this to work. The routine first tries 63 // to find the next candidate file, then tries to load it as image. 64 // As long as loading fails, the operation is repeated for the next 65 // candidate file. 66 67 class TrackerNavigator : public Navigator { 68 public: 69 TrackerNavigator( 70 const BMessenger& trackerMessenger); 71 virtual ~TrackerNavigator(); 72 73 virtual bool FindNextImage(const entry_ref& currentRef, 74 entry_ref& ref, bool next, bool rewind); 75 virtual void UpdateSelection(const entry_ref& ref); 76 77 private: 78 BMessenger fTrackerMessenger; 79 // of the window that this was launched from 80 }; 81 82 83 class FolderNavigator : public Navigator { 84 public: 85 FolderNavigator(const entry_ref& ref); 86 virtual ~FolderNavigator(); 87 88 virtual bool FindNextImage(const entry_ref& currentRef, 89 entry_ref& ref, bool next, bool rewind); 90 virtual void UpdateSelection(const entry_ref& ref); 91 92 private: 93 void _BuildEntryList(); 94 static int _CompareRefs(const entry_ref* refA, 95 const entry_ref* refB); 96 97 private: 98 BDirectory fFolder; 99 BObjectList<entry_ref> fEntries; 100 }; 101 102 103 // TODO: Remove this and use Tracker's Command.h once it is moved into the 104 // private headers! 105 namespace BPrivate { 106 const uint32 kMoveToTrash = 'Ttrs'; 107 } 108 109 110 static bool 111 entry_ref_is_file(const entry_ref& ref) 112 { 113 BEntry entry(&ref, true); 114 if (entry.InitCheck() != B_OK) 115 return false; 116 117 return entry.IsFile(); 118 } 119 120 121 // #pragma mark - 122 123 124 Navigator::Navigator() 125 { 126 } 127 128 129 Navigator::~Navigator() 130 { 131 } 132 133 134 bool 135 Navigator::IsImage(const entry_ref& ref) 136 { 137 if (!entry_ref_is_file(ref)) 138 return false; 139 140 BFile file(&ref, B_READ_ONLY); 141 if (file.InitCheck() != B_OK) 142 return false; 143 144 BTranslatorRoster* roster = BTranslatorRoster::Default(); 145 if (roster == NULL) 146 return false; 147 148 translator_info info; 149 memset(&info, 0, sizeof(translator_info)); 150 return roster->Identify(&file, NULL, &info, 0, NULL, 151 B_TRANSLATOR_BITMAP) == B_OK; 152 } 153 154 155 // #pragma mark - 156 157 158 TrackerNavigator::TrackerNavigator(const BMessenger& trackerMessenger) 159 : 160 fTrackerMessenger(trackerMessenger) 161 { 162 } 163 164 165 TrackerNavigator::~TrackerNavigator() 166 { 167 } 168 169 170 bool 171 TrackerNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& ref, 172 bool next, bool rewind) 173 { 174 // Based on GetTrackerWindowFile function from BeMail 175 if (!fTrackerMessenger.IsValid()) 176 return false; 177 178 // Ask the Tracker what the next/prev file in the window is. 179 // Continue asking for the next reference until a valid 180 // image is found. 181 entry_ref nextRef = currentRef; 182 bool foundRef = false; 183 while (!foundRef) { 184 BMessage request(B_GET_PROPERTY); 185 BMessage specifier; 186 if (rewind) 187 specifier.what = B_DIRECT_SPECIFIER; 188 else if (next) 189 specifier.what = 'snxt'; 190 else 191 specifier.what = 'sprv'; 192 specifier.AddString("property", "Entry"); 193 if (rewind) { 194 // if rewinding, ask for the ref to the 195 // first item in the directory 196 specifier.AddInt32("data", 0); 197 } else 198 specifier.AddRef("data", &nextRef); 199 request.AddSpecifier(&specifier); 200 201 BMessage reply; 202 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 203 return false; 204 if (reply.FindRef("result", &nextRef) != B_OK) 205 return false; 206 207 if (IsImage(nextRef)) 208 foundRef = true; 209 210 rewind = false; 211 // stop asking for the first ref in the directory 212 } 213 214 ref = nextRef; 215 return foundRef; 216 } 217 218 219 void 220 TrackerNavigator::UpdateSelection(const entry_ref& ref) 221 { 222 BMessage setSelection(B_SET_PROPERTY); 223 setSelection.AddSpecifier("Selection"); 224 setSelection.AddRef("data", &ref); 225 fTrackerMessenger.SendMessage(&setSelection); 226 } 227 228 229 // #pragma mark - 230 231 232 FolderNavigator::FolderNavigator(const entry_ref& ref) 233 : 234 fEntries(true) 235 { 236 node_ref nodeRef; 237 nodeRef.device = ref.device; 238 nodeRef.node = ref.directory; 239 240 fFolder.SetTo(&nodeRef); 241 _BuildEntryList(); 242 243 // TODO: monitor the directory for changes, sort it naturally 244 } 245 246 247 FolderNavigator::~FolderNavigator() 248 { 249 } 250 251 252 bool 253 FolderNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& nextRef, 254 bool next, bool rewind) 255 { 256 int32 index; 257 if (rewind) { 258 index = next ? fEntries.CountItems() : 0; 259 next = !next; 260 } else { 261 index = fEntries.BinarySearchIndex(currentRef, 262 &FolderNavigator::_CompareRefs); 263 if (next) 264 index++; 265 else 266 index--; 267 } 268 269 while (index < fEntries.CountItems() && index >= 0) { 270 const entry_ref& ref = *fEntries.ItemAt(index); 271 if (IsImage(ref)) { 272 nextRef = ref; 273 return true; 274 } else { 275 // remove non-image entries 276 delete fEntries.RemoveItemAt(index); 277 if (!next) 278 index--; 279 } 280 } 281 282 return false; 283 } 284 285 286 void 287 FolderNavigator::UpdateSelection(const entry_ref& ref) 288 { 289 // nothing to do for us here 290 } 291 292 293 void 294 FolderNavigator::_BuildEntryList() 295 { 296 fEntries.MakeEmpty(); 297 fFolder.Rewind(); 298 299 while (true) { 300 entry_ref* ref = new entry_ref(); 301 status_t status = fFolder.GetNextRef(ref); 302 if (status != B_OK) 303 break; 304 305 fEntries.AddItem(ref); 306 } 307 308 fEntries.SortItems(&FolderNavigator::_CompareRefs); 309 } 310 311 312 /*static*/ int 313 FolderNavigator::_CompareRefs(const entry_ref* refA, const entry_ref* refB) 314 { 315 // TODO: natural sorting? Collating via current locale? 316 return strcasecmp(refA->name, refB->name); 317 } 318 319 320 // #pragma mark - 321 322 323 struct CacheEntry : DoublyLinkedListLinkImpl<CacheEntry> { 324 entry_ref ref; 325 int32 page; 326 int32 pageCount; 327 BBitmap* bitmap; 328 BString type; 329 BString mimeType; 330 }; 331 332 333 struct QueueEntry { 334 entry_ref ref; 335 int32 page; 336 std::set<BMessenger> listeners; 337 }; 338 339 340 class ImageCache { 341 public: 342 ImageCache(); 343 virtual ~ImageCache(); 344 345 void RetrieveImage(const entry_ref& ref, 346 const BMessenger* target); 347 348 private: 349 static status_t _QueueWorkerThread(void* self); 350 351 status_t _RetrieveImage(QueueEntry* entry, 352 BMessage& message); 353 void _NotifyListeners(QueueEntry* entry, 354 BMessage& message); 355 356 private: 357 typedef std::pair<entry_ref, int32> ImageSelector; 358 typedef std::map<ImageSelector, CacheEntry*> CacheMap; 359 typedef std::map<ImageSelector, QueueEntry*> QueueMap; 360 typedef std::deque<QueueEntry*> QueueDeque; 361 typedef DoublyLinkedList<CacheEntry> CacheList; 362 363 BLocker fCacheLocker; 364 CacheMap fCacheMap; 365 CacheList fCacheEntriesByAge; 366 BLocker fQueueLocker; 367 QueueMap fQueueMap; 368 QueueDeque fQueue; 369 vint32 fThreadCount; 370 int32 fMaxThreadCount; 371 uint64 fBytes; 372 uint64 fMaxBytes; 373 size_t fMaxEntries; 374 }; 375 376 377 ImageCache::ImageCache() 378 : 379 fCacheLocker("image cache"), 380 fQueueLocker("image queue"), 381 fThreadCount(0), 382 fBytes(0) 383 { 384 system_info info; 385 get_system_info(&info); 386 387 fMaxThreadCount = (info.cpu_count + 1) / 2; 388 fMaxBytes = info.max_pages * B_PAGE_SIZE / 8; 389 fMaxEntries = 10; 390 } 391 392 393 ImageCache::~ImageCache() 394 { 395 // TODO: delete CacheEntries, and QueueEntries 396 } 397 398 399 void 400 ImageCache::RetrieveImage(const entry_ref& ref, const BMessenger* target) 401 { 402 // TODO! 403 } 404 405 406 /*static*/ status_t 407 ImageCache::_QueueWorkerThread(void* _self) 408 { 409 ImageCache* self = (ImageCache*)_self; 410 411 // get next queue entry 412 while (true) { 413 self->fQueueLocker.Lock(); 414 if (self->fQueue.empty()) { 415 self->fQueueLocker.Unlock(); 416 break; 417 } 418 419 QueueEntry* entry = *self->fQueue.begin(); 420 self->fQueue.pop_front(); 421 self->fQueueLocker.Unlock(); 422 423 if (entry == NULL) 424 break; 425 426 BMessage notification(kMsgImageLoaded); 427 status_t status = self->_RetrieveImage(entry, notification); 428 if (status != B_OK) 429 notification.AddInt32("error", status); 430 431 self->fQueueLocker.Lock(); 432 self->fQueueMap.erase(std::make_pair(entry->ref, entry->page)); 433 self->fQueueLocker.Unlock(); 434 435 self->_NotifyListeners(entry, notification); 436 delete entry; 437 } 438 439 atomic_add(&self->fThreadCount, -1); 440 return B_OK; 441 } 442 443 444 status_t 445 ImageCache::_RetrieveImage(QueueEntry* queueEntry, BMessage& message) 446 { 447 CacheEntry* entry = new(std::nothrow) CacheEntry(); 448 if (entry == NULL) 449 return B_NO_MEMORY; 450 451 ObjectDeleter<CacheEntry> deleter(entry); 452 453 BTranslatorRoster* roster = BTranslatorRoster::Default(); 454 if (roster == NULL) 455 return B_ERROR; 456 457 if (!entry_ref_is_file(queueEntry->ref)) 458 return B_IS_A_DIRECTORY; 459 460 BFile file; 461 status_t status = file.SetTo(&queueEntry->ref, B_READ_ONLY); 462 if (status != B_OK) 463 return status; 464 465 translator_info info; 466 memset(&info, 0, sizeof(translator_info)); 467 BMessage ioExtension; 468 469 if (queueEntry->page != 0 470 && ioExtension.AddInt32("/documentIndex", queueEntry->page) != B_OK) 471 return B_NO_MEMORY; 472 473 // TODO: rethink this! 474 #if 0 475 if (fProgressWindow != NULL) { 476 BMessage progress(kMsgProgressStatusUpdate); 477 if (ioExtension.AddMessenger("/progressMonitor", 478 fProgressWindow) == B_OK 479 && ioExtension.AddMessage("/progressMessage", &progress) == B_OK) 480 fProgressWindow->Start(); 481 } 482 #endif 483 484 // Translate image data and create a new ShowImage window 485 486 BBitmapStream outstream; 487 488 status = roster->Identify(&file, &ioExtension, &info, 0, NULL, 489 B_TRANSLATOR_BITMAP); 490 if (status == B_OK) { 491 status = roster->Translate(&file, &info, &ioExtension, &outstream, 492 B_TRANSLATOR_BITMAP); 493 } 494 495 #if 0 496 if (fProgressWindow != NULL) 497 fProgressWindow->Stop(); 498 #endif 499 500 if (status != B_OK) 501 return status; 502 503 BBitmap* bitmap; 504 if (outstream.DetachBitmap(&bitmap) != B_OK) 505 return B_ERROR; 506 507 entry->ref = queueEntry->ref; 508 entry->page = queueEntry->page; 509 entry->bitmap = bitmap; 510 entry->type = info.name; 511 entry->mimeType = info.MIME; 512 513 // get the number of documents (pages) if it has been supplied 514 int32 documentCount = 0; 515 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK 516 && documentCount > 0) 517 entry->pageCount = documentCount; 518 else 519 entry->pageCount = 1; 520 521 message.AddString("type", info.name); 522 message.AddString("mime", info.MIME); 523 message.AddRef("ref", &entry->ref); 524 message.AddInt32("page", entry->page); 525 message.AddPointer("bitmap", (void*)bitmap); 526 527 deleter.Detach(); 528 529 fCacheLocker.Lock(); 530 531 fCacheMap.insert(std::make_pair( 532 std::make_pair(entry->ref, entry->page), entry)); 533 fCacheEntriesByAge.Add(entry); 534 535 fBytes += bitmap->BitsLength(); 536 537 while (fBytes > fMaxBytes || fCacheMap.size() > fMaxEntries) { 538 if (fCacheMap.size() <= 2) 539 break; 540 541 // Remove the oldest entry 542 entry = fCacheEntriesByAge.RemoveHead(); 543 fBytes -= entry->bitmap->BitsLength(); 544 fCacheMap.erase(std::make_pair(entry->ref, entry->page)); 545 delete entry; 546 } 547 548 fCacheLocker.Unlock(); 549 return B_OK; 550 } 551 552 553 void 554 ImageCache::_NotifyListeners(QueueEntry* entry, BMessage& message) 555 { 556 std::set<BMessenger>::iterator iterator = entry->listeners.begin(); 557 for (; iterator != entry->listeners.end(); iterator++) { 558 iterator->SendMessage(&message); 559 } 560 } 561 562 563 // #pragma mark - 564 565 566 ImageFileNavigator::ImageFileNavigator(const BMessenger& target, 567 const entry_ref& ref, const BMessenger& trackerMessenger) 568 : 569 fTarget(target), 570 fProgressWindow(NULL), 571 fDocumentIndex(1), 572 fDocumentCount(1) 573 { 574 if (trackerMessenger.IsValid()) 575 fNavigator = new TrackerNavigator(trackerMessenger); 576 else 577 fNavigator = new FolderNavigator(ref); 578 } 579 580 581 ImageFileNavigator::~ImageFileNavigator() 582 { 583 delete fNavigator; 584 } 585 586 587 void 588 ImageFileNavigator::SetProgressWindow(ProgressWindow* progressWindow) 589 { 590 fProgressWindow = progressWindow; 591 } 592 593 594 status_t 595 ImageFileNavigator::LoadImage(const entry_ref& ref, int32 page) 596 { 597 BTranslatorRoster* roster = BTranslatorRoster::Default(); 598 if (roster == NULL) 599 return B_ERROR; 600 601 if (!entry_ref_is_file(ref)) 602 return B_ERROR; 603 604 BFile file(&ref, B_READ_ONLY); 605 translator_info info; 606 memset(&info, 0, sizeof(translator_info)); 607 BMessage ioExtension; 608 609 if (page != 0 && ioExtension.AddInt32("/documentIndex", page) != B_OK) 610 return B_ERROR; 611 612 if (fProgressWindow != NULL) { 613 BMessage progress(kMsgProgressStatusUpdate); 614 if (ioExtension.AddMessenger("/progressMonitor", 615 fProgressWindow) == B_OK 616 && ioExtension.AddMessage("/progressMessage", &progress) == B_OK) 617 fProgressWindow->Start(); 618 } 619 620 // Translate image data and create a new ShowImage window 621 622 BBitmapStream outstream; 623 624 status_t status = roster->Identify(&file, &ioExtension, &info, 0, NULL, 625 B_TRANSLATOR_BITMAP); 626 if (status == B_OK) { 627 status = roster->Translate(&file, &info, &ioExtension, &outstream, 628 B_TRANSLATOR_BITMAP); 629 } 630 631 if (fProgressWindow != NULL) 632 fProgressWindow->Stop(); 633 634 if (status != B_OK) 635 return status; 636 637 BBitmap* bitmap; 638 if (outstream.DetachBitmap(&bitmap) != B_OK) 639 return B_ERROR; 640 641 fCurrentRef = ref; 642 fDocumentIndex = page; 643 644 // get the number of documents (pages) if it has been supplied 645 int32 documentCount = 0; 646 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK 647 && documentCount > 0) 648 fDocumentCount = documentCount; 649 else 650 fDocumentCount = 1; 651 652 BMessage message(kMsgImageLoaded); 653 message.AddString("type", info.name); 654 message.AddString("mime", info.MIME); 655 message.AddRef("ref", &ref); 656 message.AddInt32("page", page); 657 message.AddPointer("bitmap", (void*)bitmap); 658 status = fTarget.SendMessage(&message); 659 if (status != B_OK) { 660 delete bitmap; 661 return status; 662 } 663 664 return B_OK; 665 } 666 667 668 void 669 ImageFileNavigator::GetPath(BString* outPath) 670 { 671 BEntry entry(&fCurrentRef); 672 BPath path; 673 if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK) 674 outPath->SetTo(""); 675 else 676 outPath->SetTo(path.Path()); 677 } 678 679 680 int32 681 ImageFileNavigator::CurrentPage() 682 { 683 return fDocumentIndex; 684 } 685 686 687 int32 688 ImageFileNavigator::PageCount() 689 { 690 return fDocumentCount; 691 } 692 693 694 bool 695 ImageFileNavigator::FirstPage() 696 { 697 if (fDocumentIndex != 1) { 698 LoadImage(fCurrentRef, 1); 699 return true; 700 } 701 return false; 702 } 703 704 705 bool 706 ImageFileNavigator::LastPage() 707 { 708 if (fDocumentIndex != fDocumentCount) { 709 LoadImage(fCurrentRef, fDocumentCount); 710 return true; 711 } 712 return false; 713 } 714 715 716 bool 717 ImageFileNavigator::NextPage() 718 { 719 if (fDocumentIndex < fDocumentCount) { 720 LoadImage(fCurrentRef, ++fDocumentIndex); 721 return true; 722 } 723 return false; 724 } 725 726 727 bool 728 ImageFileNavigator::PreviousPage() 729 { 730 if (fDocumentIndex > 1) { 731 LoadImage(fCurrentRef, --fDocumentIndex); 732 return true; 733 } 734 return false; 735 } 736 737 738 bool 739 ImageFileNavigator::GoToPage(int32 page) 740 { 741 if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) { 742 fDocumentIndex = page; 743 LoadImage(fCurrentRef, fDocumentIndex); 744 return true; 745 } 746 return false; 747 } 748 749 750 void 751 ImageFileNavigator::FirstFile() 752 { 753 _LoadNextImage(true, true); 754 } 755 756 757 void 758 ImageFileNavigator::NextFile() 759 { 760 _LoadNextImage(true, false); 761 } 762 763 764 void 765 ImageFileNavigator::PreviousFile() 766 { 767 _LoadNextImage(false, false); 768 } 769 770 771 bool 772 ImageFileNavigator::HasNextFile() 773 { 774 entry_ref ref; 775 return fNavigator->FindNextImage(fCurrentRef, ref, true, false); 776 } 777 778 779 bool 780 ImageFileNavigator::HasPreviousFile() 781 { 782 entry_ref ref; 783 return fNavigator->FindNextImage(fCurrentRef, ref, false, false); 784 } 785 786 787 /*! Moves the current file into the trash. 788 Returns true if a new file is being loaded, false if not. 789 */ 790 bool 791 ImageFileNavigator::MoveFileToTrash() 792 { 793 entry_ref nextRef; 794 if (!fNavigator->FindNextImage(fCurrentRef, nextRef, true, false) 795 && !fNavigator->FindNextImage(fCurrentRef, nextRef, false, false)) 796 nextRef.device = -1; 797 798 // Move image to Trash 799 BMessage trash(BPrivate::kMoveToTrash); 800 trash.AddRef("refs", &fCurrentRef); 801 802 // We create our own messenger because the member fTrackerMessenger 803 // could be invalid 804 BMessenger tracker(kTrackerSignature); 805 if (tracker.SendMessage(&trash) != B_OK) 806 return true; 807 808 if (nextRef.device != -1 && LoadImage(nextRef) == B_OK) { 809 fNavigator->UpdateSelection(nextRef); 810 return true; 811 } 812 813 return false; 814 } 815 816 817 // #pragma mark - 818 819 820 status_t 821 ImageFileNavigator::_LoadNextImage(bool next, bool rewind) 822 { 823 entry_ref currentRef = fCurrentRef; 824 entry_ref ref; 825 if (fNavigator->FindNextImage(currentRef, ref, next, rewind)) { 826 // Keep trying to load images until: 827 // 1. The image loads successfully 828 // 2. The last file in the directory is found (for find next or find 829 // first) 830 // 3. The first file in the directory is found (for find prev) 831 // 4. The call to _FindNextImage fails for any other reason 832 while (LoadImage(ref) != B_OK) { 833 currentRef = ref; 834 if (!fNavigator->FindNextImage(currentRef, ref, next, false)) 835 return B_ENTRY_NOT_FOUND; 836 } 837 fNavigator->UpdateSelection(fCurrentRef); 838 return B_OK; 839 } 840 return B_ENTRY_NOT_FOUND; 841 } 842 843 844