1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 // Dedicated to BModel 36 37 // ToDo: 38 // Consider moving iconFrom logic to BPose 39 // use a more efficient way of storing file type and preferred app strings 40 41 42 #include "Model.h" 43 44 #include <stdlib.h> 45 #include <strings.h> 46 47 #include <fs_info.h> 48 #include <fs_attr.h> 49 50 #include <AppDefs.h> 51 #include <Bitmap.h> 52 #include <Catalog.h> 53 #include <Debug.h> 54 #include <Directory.h> 55 #include <Entry.h> 56 #include <File.h> 57 #include <Locale.h> 58 #include <NodeInfo.h> 59 #include <NodeMonitor.h> 60 #include <Path.h> 61 #include <SymLink.h> 62 #include <Query.h> 63 #include <Volume.h> 64 #include <VolumeRoster.h> 65 66 #include "Attributes.h" 67 #include "Bitmaps.h" 68 #include "FindPanel.h" 69 #include "FSUtils.h" 70 #include "MimeTypes.h" 71 #include "Thumbnails.h" 72 #include "Tracker.h" 73 #include "Utilities.h" 74 75 76 #undef B_TRANSLATION_CONTEXT 77 #define B_TRANSLATION_CONTEXT "Model" 78 79 80 #ifdef CHECK_OPEN_MODEL_LEAKS 81 BObjectList<Model>* writableOpenModelList = NULL; 82 BObjectList<Model>* readOnlyOpenModelList = NULL; 83 #endif 84 85 86 // #pragma mark - Model() 87 88 89 Model::Model() 90 : 91 fPreferredAppName(NULL), 92 fBaseType(kUnknownNode), 93 fIconFrom(kUnknownSource), 94 fWritable(false), 95 fNode(NULL), 96 fStatus(B_NO_INIT), 97 fHasLocalizedName(false), 98 fLocalizedNameIsCached(false) 99 { 100 } 101 102 103 Model::Model(const Model& other) 104 : 105 fEntryRef(other.fEntryRef), 106 fMimeType(other.fMimeType), 107 fPreferredAppName(NULL), 108 fBaseType(other.fBaseType), 109 fIconFrom(other.fIconFrom), 110 fWritable(false), 111 fNode(NULL), 112 fLocalizedName(other.fLocalizedName), 113 fHasLocalizedName(other.fHasLocalizedName), 114 fLocalizedNameIsCached(other.fLocalizedNameIsCached) 115 { 116 fStatBuf.st_dev = other.NodeRef()->device; 117 fStatBuf.st_ino = other.NodeRef()->node; 118 119 if (other.IsSymLink() && other.LinkTo()) 120 fLinkTo = new Model(*other.LinkTo()); 121 122 fStatus = OpenNode(other.IsNodeOpenForWriting()); 123 if (fStatus == B_OK) { 124 ASSERT(fNode); 125 fNode->GetStat(&fStatBuf); 126 ASSERT(fStatBuf.st_dev == other.NodeRef()->device); 127 ASSERT(fStatBuf.st_ino == other.NodeRef()->node); 128 } 129 if (!other.IsNodeOpen()) 130 CloseNode(); 131 } 132 133 134 Model::Model(const node_ref* dirNode, const node_ref* node, const char* name, 135 bool open, bool writable) 136 : 137 fPreferredAppName(NULL), 138 fWritable(false), 139 fNode(NULL), 140 fHasLocalizedName(false), 141 fLocalizedNameIsCached(false) 142 { 143 SetTo(dirNode, node, name, open, writable); 144 } 145 146 147 Model::Model(const BEntry* entry, bool open, bool writable) 148 : 149 fPreferredAppName(NULL), 150 fWritable(false), 151 fNode(NULL), 152 fHasLocalizedName(false), 153 fLocalizedNameIsCached(false) 154 { 155 SetTo(entry, open, writable); 156 } 157 158 159 Model::Model(const entry_ref* ref, bool traverse, bool open, bool writable) 160 : 161 fPreferredAppName(NULL), 162 fBaseType(kUnknownNode), 163 fIconFrom(kUnknownSource), 164 fWritable(false), 165 fNode(NULL), 166 fHasLocalizedName(false), 167 fLocalizedNameIsCached(false) 168 { 169 BEntry entry(ref, traverse); 170 fStatus = entry.InitCheck(); 171 if (fStatus == B_OK) 172 SetTo(&entry, open, writable); 173 } 174 175 176 void 177 Model::DeletePreferredAppVolumeNameLinkTo() 178 { 179 if (IsSymLink()) { 180 Model* tmp = fLinkTo; 181 // deal with link to link to self 182 fLinkTo = NULL; 183 delete tmp; 184 } else if (IsVolume()) 185 free(fVolumeName); 186 else 187 free(fPreferredAppName); 188 189 fPreferredAppName = NULL; 190 } 191 192 193 Model::~Model() 194 { 195 #ifdef CHECK_OPEN_MODEL_LEAKS 196 if (writableOpenModelList != NULL) 197 writableOpenModelList->RemoveItem(this); 198 199 if (readOnlyOpenModelList != NULL) 200 readOnlyOpenModelList->RemoveItem(this); 201 #endif 202 203 DeletePreferredAppVolumeNameLinkTo(); 204 if (IconCache::NeedsDeletionNotification((IconSource)fIconFrom)) { 205 // this check allows us to use temporary Model in the IconCache 206 // without the danger of a deadlock 207 IconCache::sIconCache->Deleting(this); 208 } 209 #if xDEBUG 210 if (fNode != NULL) 211 PRINT(("destructor closing node for %s\n", Name())); 212 #endif 213 214 delete fNode; 215 } 216 217 218 status_t 219 Model::SetTo(const BEntry* entry, bool open, bool writable) 220 { 221 delete fNode; 222 fNode = NULL; 223 DeletePreferredAppVolumeNameLinkTo(); 224 fIconFrom = kUnknownSource; 225 fBaseType = kUnknownNode; 226 fMimeType = ""; 227 228 fStatus = entry->GetRef(&fEntryRef); 229 if (fStatus != B_OK) 230 return fStatus; 231 232 fStatus = entry->GetStat(&fStatBuf); 233 if (fStatus != B_OK) 234 return fStatus; 235 236 fStatus = OpenNode(writable); 237 if (!open) 238 CloseNode(); 239 240 return fStatus; 241 } 242 243 244 status_t 245 Model::SetTo(const entry_ref* newRef, bool traverse, bool open, bool writable) 246 { 247 delete fNode; 248 fNode = NULL; 249 DeletePreferredAppVolumeNameLinkTo(); 250 fIconFrom = kUnknownSource; 251 fBaseType = kUnknownNode; 252 fMimeType = ""; 253 254 BEntry tmpEntry(newRef, traverse); 255 fStatus = tmpEntry.InitCheck(); 256 if (fStatus != B_OK) 257 return fStatus; 258 259 if (traverse) 260 tmpEntry.GetRef(&fEntryRef); 261 else 262 fEntryRef = *newRef; 263 264 fStatus = tmpEntry.GetStat(&fStatBuf); 265 if (fStatus != B_OK) 266 return fStatus; 267 268 fStatus = OpenNode(writable); 269 if (!open) 270 CloseNode(); 271 272 return fStatus; 273 } 274 275 276 status_t 277 Model::SetTo(const node_ref* dirNode, const node_ref* nodeRef, 278 const char* name, bool open, bool writable) 279 { 280 delete fNode; 281 fNode = NULL; 282 DeletePreferredAppVolumeNameLinkTo(); 283 fIconFrom = kUnknownSource; 284 fBaseType = kUnknownNode; 285 fMimeType = ""; 286 287 fStatBuf.st_dev = nodeRef->device; 288 fStatBuf.st_ino = nodeRef->node; 289 fEntryRef.device = dirNode->device; 290 fEntryRef.directory = dirNode->node; 291 fEntryRef.name = strdup(name); 292 293 BEntry tmpNode(&fEntryRef); 294 fStatus = tmpNode.InitCheck(); 295 if (fStatus != B_OK) 296 return fStatus; 297 298 fStatus = tmpNode.GetStat(&fStatBuf); 299 if (fStatus != B_OK) 300 return fStatus; 301 302 fStatus = OpenNode(writable); 303 304 if (!open) 305 CloseNode(); 306 307 return fStatus; 308 } 309 310 311 status_t 312 Model::InitCheck() const 313 { 314 return fStatus; 315 } 316 317 318 int 319 Model::CompareFolderNamesFirst(const Model* compareModel) const 320 { 321 if (compareModel == NULL) 322 return -1; 323 324 const Model* resolvedCompareModel = compareModel->ResolveIfLink(); 325 const Model* resolvedMe = ResolveIfLink(); 326 327 bool meIsDirOrVolume = resolvedMe->IsDirectory() || resolvedMe->IsVolume() 328 || resolvedMe->IsVirtualDirectory(); 329 bool otherIsDirOrVolume = resolvedCompareModel->IsDirectory() 330 || resolvedCompareModel->IsVolume() 331 || resolvedCompareModel->IsVirtualDirectory(); 332 333 if (meIsDirOrVolume) { 334 if (!otherIsDirOrVolume) 335 return -1; 336 } else if (otherIsDirOrVolume) 337 return 1; 338 339 return NaturalCompare(Name(), compareModel->Name()); 340 } 341 342 343 const char* 344 Model::Name() const 345 { 346 static const char* kRootNodeName = B_TRANSLATE_MARK(B_DISKS_DIR_NAME); 347 static const char* kTrashNodeName = B_TRANSLATE_MARK(B_TRASH_DIR_NAME); 348 static const char* kDesktopNodeName = B_TRANSLATE_MARK(B_DESKTOP_DIR_NAME); 349 350 switch (fBaseType) { 351 case kRootNode: 352 return B_TRANSLATE_NOCOLLECT(kRootNodeName); 353 354 case kVolumeNode: 355 if (fVolumeName != NULL) 356 return fVolumeName; 357 break; 358 359 case kTrashNode: 360 return B_TRANSLATE_NOCOLLECT(kTrashNodeName); 361 362 case kDesktopNode: 363 return B_TRANSLATE_NOCOLLECT(kDesktopNodeName); 364 365 default: 366 break; 367 } 368 369 if (fHasLocalizedName && gLocalizedNamePreferred) 370 return fLocalizedName.String(); 371 else 372 return fEntryRef.name; 373 } 374 375 376 status_t 377 Model::OpenNode(bool writable) 378 { 379 if (IsNodeOpen() && (writable == IsNodeOpenForWriting())) 380 return B_OK; 381 382 OpenNodeCommon(writable); 383 384 return fStatus; 385 } 386 387 388 status_t 389 Model::UpdateStatAndOpenNode(bool writable) 390 { 391 if (IsNodeOpen() && (writable == IsNodeOpenForWriting())) 392 return B_OK; 393 394 // try reading the stat structure again 395 BEntry tmpEntry(&fEntryRef); 396 fStatus = tmpEntry.InitCheck(); 397 if (fStatus != B_OK) 398 return fStatus; 399 400 fStatus = tmpEntry.GetStat(&fStatBuf); 401 if (fStatus != B_OK) 402 return fStatus; 403 404 OpenNodeCommon(writable); 405 406 return fStatus; 407 } 408 409 410 status_t 411 Model::OpenNodeCommon(bool writable) 412 { 413 #if xDEBUG 414 PRINT(("opening node for %s\n", Name())); 415 #endif 416 417 #ifdef CHECK_OPEN_MODEL_LEAKS 418 if (writableOpenModelList != NULL) 419 writableOpenModelList->RemoveItem(this); 420 421 if (readOnlyOpenModelList != NULL) 422 readOnlyOpenModelList->RemoveItem(this); 423 #endif 424 425 if (fBaseType == kUnknownNode) 426 SetupBaseType(); 427 428 switch (fBaseType) { 429 case kPlainNode: 430 case kExecutableNode: 431 case kQueryNode: 432 case kQueryTemplateNode: 433 case kVirtualDirectoryNode: 434 // open or reopen 435 delete fNode; 436 fNode = new BFile(&fEntryRef, 437 (uint32)(writable ? O_RDWR : O_RDONLY)); 438 break; 439 440 case kDirectoryNode: 441 case kVolumeNode: 442 case kRootNode: 443 case kTrashNode: 444 case kDesktopNode: 445 if (!IsNodeOpen()) 446 fNode = new BDirectory(&fEntryRef); 447 448 if (fBaseType == kDirectoryNode 449 && static_cast<BDirectory*>(fNode)->IsRootDirectory()) { 450 // promote from directory to volume 451 fBaseType = kVolumeNode; 452 } 453 break; 454 455 case kLinkNode: 456 if (!IsNodeOpen()) { 457 BEntry entry(&fEntryRef); 458 fNode = new BSymLink(&entry); 459 } 460 break; 461 462 default: 463 #if DEBUG 464 PrintToStream(); 465 #endif 466 TRESPASS(); 467 // this can only happen if GetStat failed before, 468 // in which case we shouldn't be here 469 470 // ToDo: Obviously, we can also be here if the type could not 471 // be determined, for example for block devices (so the TRESPASS() 472 // macro shouldn't be used here)! 473 return fStatus = B_ERROR; 474 } 475 476 fStatus = fNode->InitCheck(); 477 if (fStatus != B_OK) { 478 delete fNode; 479 fNode = NULL; 480 // original code snoozed an error here and returned B_OK 481 return fStatus; 482 } 483 484 fWritable = writable; 485 486 if (fMimeType.Length() <= 0) 487 FinishSettingUpType(); 488 489 #ifdef CHECK_OPEN_MODEL_LEAKS 490 if (fWritable) { 491 if (!writableOpenModelList) { 492 TRACE(); 493 writableOpenModelList = new BObjectList<Model>(100); 494 } 495 writableOpenModelList->AddItem(this); 496 } else { 497 if (!readOnlyOpenModelList) { 498 TRACE(); 499 readOnlyOpenModelList = new BObjectList<Model>(100); 500 } 501 readOnlyOpenModelList->AddItem(this); 502 } 503 #endif 504 505 if (gLocalizedNamePreferred) 506 CacheLocalizedName(); 507 508 return fStatus; 509 } 510 511 512 void 513 Model::CloseNode() 514 { 515 #if xDEBUG 516 PRINT(("closing node for %s\n", Name())); 517 #endif 518 519 #ifdef CHECK_OPEN_MODEL_LEAKS 520 if (writableOpenModelList != NULL) 521 writableOpenModelList->RemoveItem(this); 522 523 if (readOnlyOpenModelList != NULL) 524 readOnlyOpenModelList->RemoveItem(this); 525 #endif 526 527 delete fNode; 528 fNode = NULL; 529 } 530 531 532 bool 533 Model::IsNodeOpen() const 534 { 535 return fNode != NULL; 536 } 537 538 539 540 bool 541 Model::IsNodeOpenForWriting() const 542 { 543 return fNode != NULL && fWritable; 544 } 545 546 547 void 548 Model::SetupBaseType() 549 { 550 switch (fStatBuf.st_mode & S_IFMT) { 551 case S_IFDIR: 552 // folder 553 fBaseType = kDirectoryNode; 554 break; 555 556 case S_IFREG: 557 // regular file 558 if ((fStatBuf.st_mode & S_IXUSR) != 0) { 559 // executable 560 fBaseType = kExecutableNode; 561 } else { 562 // non-executable 563 fBaseType = kPlainNode; 564 } 565 break; 566 567 case S_IFLNK: 568 // symlink 569 fBaseType = kLinkNode; 570 break; 571 572 default: 573 fBaseType = kUnknownNode; 574 break; 575 } 576 } 577 578 579 void 580 Model::CacheLocalizedName() 581 { 582 if (!fLocalizedNameIsCached) { 583 fLocalizedNameIsCached = true; 584 if (BLocaleRoster::Default()->GetLocalizedFileName( 585 fLocalizedName, fEntryRef, true) == B_OK) 586 fHasLocalizedName = true; 587 else 588 fHasLocalizedName = false; 589 } 590 } 591 592 593 void 594 Model::FinishSettingUpType() 595 { 596 char type[B_MIME_TYPE_LENGTH]; 597 BEntry entry; 598 599 // While we are reading the node, do a little snooping to see if it even 600 // makes sense to look for a node-based icon. This serves as a hint to the 601 // icon cache, allowing it to not hit the disk again for models that do not 602 // have an icon defined by the node. 603 if (fBaseType != kLinkNode && !CheckAppIconHint()) 604 fIconFrom = kUnknownNotFromNode; 605 606 if (fBaseType != kDirectoryNode 607 && fBaseType != kVolumeNode 608 && fBaseType != kLinkNode 609 && IsNodeOpen()) { 610 BNodeInfo info(fNode); 611 612 // check if a specific mime type is set 613 if (info.GetType(type) == B_OK) { 614 // node has a specific mime type 615 fMimeType = type; 616 if (strcmp(type, B_QUERY_MIMETYPE) == 0) 617 fBaseType = kQueryNode; 618 else if (strcmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0) 619 fBaseType = kQueryTemplateNode; 620 else if (strcmp(type, kVirtualDirectoryMimeType) == 0) 621 fBaseType = kVirtualDirectoryNode; 622 623 attr_info thumb; 624 if (fNode->GetAttrInfo(kAttrThumbnail, &thumb) == B_OK 625 || ShouldGenerateThumbnail(type)) { 626 fIconFrom = kNode; 627 } 628 629 if (info.GetPreferredApp(type) == B_OK) { 630 if (fPreferredAppName) 631 DeletePreferredAppVolumeNameLinkTo(); 632 633 if (*type != '\0') 634 fPreferredAppName = strdup(type); 635 } 636 } 637 } 638 639 switch (fBaseType) { 640 case kDirectoryNode: 641 entry.SetTo(&fEntryRef); 642 if (entry.InitCheck() == B_OK) { 643 if (FSIsTrashDir(&entry)) 644 fBaseType = kTrashNode; 645 else if (FSIsDeskDir(&entry)) 646 fBaseType = kDesktopNode; 647 } 648 649 fMimeType = B_DIR_MIMETYPE; 650 // should use a shared string here 651 if (IsNodeOpen()) { 652 BNodeInfo info(fNode); 653 if (info.GetType(type) == B_OK) 654 fMimeType = type; 655 656 if (fIconFrom == kUnknownNotFromNode 657 && WellKnowEntryList::Match(NodeRef()) 658 > (directory_which)-1) { 659 // one of home, beos, system, boot, etc. 660 fIconFrom = kTrackerSupplied; 661 } 662 } 663 break; 664 665 case kVolumeNode: 666 { 667 if (NodeRef()->node == fEntryRef.directory 668 && NodeRef()->device == fEntryRef.device) { 669 // promote from volume to file system root 670 fBaseType = kRootNode; 671 fMimeType = B_ROOT_MIMETYPE; 672 break; 673 } 674 675 // volumes have to have a B_VOLUME_MIMETYPE type 676 fMimeType = B_VOLUME_MIMETYPE; 677 if (fIconFrom == kUnknownNotFromNode) { 678 if (WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) 679 fIconFrom = kTrackerSupplied; 680 else 681 fIconFrom = kVolume; 682 } 683 684 char name[B_FILE_NAME_LENGTH]; 685 BVolume volume(NodeRef()->device); 686 if (volume.InitCheck() == B_OK && volume.GetName(name) == B_OK) { 687 if (fVolumeName != NULL) 688 DeletePreferredAppVolumeNameLinkTo(); 689 690 fVolumeName = strdup(name); 691 } 692 #if DEBUG 693 else 694 PRINT(("get volume name failed for %s\n", fEntryRef.name)); 695 #endif 696 break; 697 } 698 699 case kLinkNode: 700 fMimeType = B_LINK_MIMETYPE; 701 // should use a shared string here 702 break; 703 704 case kExecutableNode: 705 if (IsNodeOpen()) { 706 char signature[B_MIME_TYPE_LENGTH]; 707 if (GetAppSignatureFromAttr(dynamic_cast<BFile*>(fNode), 708 signature) == B_OK) { 709 if (fPreferredAppName) 710 DeletePreferredAppVolumeNameLinkTo(); 711 712 if (signature[0]) 713 fPreferredAppName = strdup(signature); 714 } 715 } 716 if (fMimeType.Length() <= 0) 717 fMimeType = B_APP_MIME_TYPE; 718 // should use a shared string here 719 break; 720 721 default: 722 if (fMimeType.Length() <= 0) 723 fMimeType = B_FILE_MIMETYPE; 724 break; 725 } 726 } 727 728 729 bool 730 Model::ShouldUseWellKnownIcon() const 731 { 732 if (fBaseType == kDirectoryNode || fBaseType == kVolumeNode 733 || fBaseType == kTrashNode || fBaseType == kDesktopNode) 734 return !CheckAppIconHint(); 735 return false; 736 } 737 738 739 bool 740 Model::CheckAppIconHint() const 741 { 742 attr_info info; 743 if (fNode == NULL) { 744 // Node is not open. 745 return false; 746 } 747 748 if (fNode->GetAttrInfo(kAttrIcon, &info) == B_OK) { 749 // Node has a vector icon 750 return true; 751 } 752 753 if (fNode->GetAttrInfo(kAttrMiniIcon, &info) == B_OK 754 && fNode->GetAttrInfo(kAttrLargeIcon, &info) == B_OK) { 755 // Node has a mini _and_ large icon 756 return true; 757 } 758 759 // If there isn't either of these, we can't use the icon attribute from the node. 760 return false; 761 } 762 763 764 void 765 Model::ResetIconFrom() 766 { 767 BModelOpener opener(this); 768 769 if (InitCheck() != B_OK) 770 return; 771 772 if (ShouldUseWellKnownIcon()) { 773 BDirectory* directory = dynamic_cast<BDirectory*>(fNode); 774 if (WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) { 775 fIconFrom = kTrackerSupplied; 776 return; 777 } else if (directory != NULL && directory->IsRootDirectory()) { 778 fIconFrom = kVolume; 779 return; 780 } 781 } 782 fIconFrom = kUnknownSource; 783 } 784 785 786 const char* 787 Model::PreferredAppSignature() const 788 { 789 if (IsVolume() || IsSymLink()) 790 return ""; 791 792 return fPreferredAppName ? fPreferredAppName : ""; 793 } 794 795 796 void 797 Model::SetPreferredAppSignature(const char* signature) 798 { 799 ASSERT(!IsVolume() && !IsSymLink()); 800 ASSERT(signature != fPreferredAppName); 801 // self assignment should not be an option 802 803 free(fPreferredAppName); 804 if (signature) 805 fPreferredAppName = strdup(signature); 806 else 807 fPreferredAppName = NULL; 808 } 809 810 811 const Model* 812 Model::ResolveIfLink() const 813 { 814 if (!IsSymLink()) 815 return this; 816 817 if (!fLinkTo) 818 return this; 819 820 return fLinkTo; 821 } 822 823 824 Model* 825 Model::ResolveIfLink() 826 { 827 if (!IsSymLink()) 828 return this; 829 830 if (!fLinkTo) 831 return this; 832 833 return fLinkTo; 834 } 835 836 837 void 838 Model::SetLinkTo(Model* model) 839 { 840 ASSERT(IsSymLink()); 841 ASSERT(!fLinkTo || (fLinkTo != model)); 842 843 delete fLinkTo; 844 fLinkTo = model; 845 } 846 847 848 // #pragma mark - Node monitor updating methods 849 850 851 void 852 Model::UpdateEntryRef(const node_ref* dirNode, const char* name) 853 { 854 if (IsVolume()) { 855 if (fVolumeName != NULL) 856 DeletePreferredAppVolumeNameLinkTo(); 857 858 fVolumeName = strdup(name); 859 } 860 861 fEntryRef.device = dirNode->device; 862 fEntryRef.directory = dirNode->node; 863 864 if (fEntryRef.name != NULL && strcmp(fEntryRef.name, name) == 0) 865 return; 866 867 fEntryRef.set_name(name); 868 } 869 870 871 status_t 872 Model::WatchVolumeAndMountPoint(uint32 , BHandler* target) 873 { 874 ASSERT(IsVolume()); 875 876 if (fEntryRef.name != NULL && fVolumeName != NULL 877 && strcmp(fEntryRef.name, "boot") == 0) { 878 // watch mount point for boot volume 879 BString bootMountPoint("/"); 880 bootMountPoint += fVolumeName; 881 BEntry mountPointEntry(bootMountPoint.String()); 882 Model mountPointModel(&mountPointEntry); 883 884 TTracker::WatchNode(mountPointModel.NodeRef(), 885 B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, target); 886 } 887 888 return TTracker::WatchNode(NodeRef(), 889 B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, target); 890 } 891 892 893 bool 894 Model::AttrChanged(const char* attrName) 895 { 896 // called on an attribute changed node monitor 897 // sync up cached values of mime type and preferred app and 898 // return true if icon needs updating 899 900 ASSERT(IsNodeOpen()); 901 if (attrName != NULL 902 && (strcmp(attrName, kAttrIcon) == 0 903 || strcmp(attrName, kAttrMiniIcon) == 0 904 || strcmp(attrName, kAttrLargeIcon) == 0 905 || strcmp(attrName, kAttrThumbnail) == 0)) { 906 return true; 907 } 908 909 if (attrName == NULL 910 || strcmp(attrName, kAttrMIMEType) == 0 911 || strcmp(attrName, kAttrPreferredApp) == 0) { 912 char type[B_MIME_TYPE_LENGTH]; 913 BNodeInfo info(fNode); 914 if (info.GetType(type) != B_OK) 915 fMimeType = ""; 916 else { 917 // node has a specific mime type 918 fMimeType = type; 919 if (!IsVolume() && !IsSymLink() 920 && info.GetPreferredApp(type) == B_OK) { 921 SetPreferredAppSignature(type); 922 } 923 } 924 925 #if xDEBUG 926 if (fIconFrom != kNode) { 927 PRINT(("%s, %s:updating icon because file type changed\n", 928 Name(), attrName != NULL ? attrName : "")); 929 } else { 930 PRINT(("Not updating icon even though type changed " 931 "because icon is from node.\n")); 932 } 933 #endif 934 935 return fIconFrom != kNode; 936 // update icon unless it is coming from a node 937 } 938 939 return attrName == NULL; 940 } 941 942 943 bool 944 Model::StatChanged() 945 { 946 ASSERT(IsNodeOpen()); 947 mode_t oldMode = fStatBuf.st_mode; 948 fStatus = fNode->GetStat(&fStatBuf); 949 950 if (oldMode != fStatBuf.st_mode) { 951 bool forWriting = IsNodeOpenForWriting(); 952 CloseNode(); 953 //SetupBaseType(); 954 // the node type can't change with a stat update... 955 OpenNodeCommon(forWriting); 956 return true; 957 } 958 959 return false; 960 } 961 962 963 // #pragma mark - Mime handling methods 964 965 966 bool 967 Model::IsDropTarget(const Model* forDocument, bool traverse) const 968 { 969 switch (CanHandleDrops()) { 970 case kCanHandle: 971 return true; 972 973 case kCannotHandle: 974 return false; 975 976 default: 977 break; 978 } 979 980 if (forDocument == NULL) 981 return true; 982 983 if (traverse) { 984 BEntry entry(forDocument->EntryRef(), true); 985 if (entry.InitCheck() != B_OK) 986 return false; 987 988 BFile file(&entry, O_RDONLY); 989 BNodeInfo mime(&file); 990 991 if (mime.InitCheck() != B_OK) 992 return false; 993 994 char mimeType[B_MIME_TYPE_LENGTH]; 995 mime.GetType(mimeType); 996 997 return SupportsMimeType(mimeType, 0) != kDoesNotSupportType; 998 } 999 1000 // do some mime-based matching 1001 const char* documentMimeType = forDocument->MimeType(); 1002 if (documentMimeType == NULL) 1003 return false; 1004 1005 return SupportsMimeType(documentMimeType, 0) != kDoesNotSupportType; 1006 } 1007 1008 1009 Model::CanHandleResult 1010 Model::CanHandleDrops() const 1011 { 1012 if (IsDirectory()) { 1013 // directories take anything 1014 // resolve permissions here 1015 return kCanHandle; 1016 } 1017 1018 if (IsSymLink()) { 1019 // descend into symlink and try again on it's target 1020 1021 BEntry entry(&fEntryRef, true); 1022 if (entry.InitCheck() != B_OK) 1023 return kCannotHandle; 1024 1025 if (entry == BEntry(EntryRef())) 1026 // self-referencing link, avoid infinite recursion 1027 return kCannotHandle; 1028 1029 Model model(&entry); 1030 if (model.InitCheck() != B_OK) 1031 return kCannotHandle; 1032 1033 return model.CanHandleDrops(); 1034 } 1035 1036 if (IsExecutable()) 1037 return kNeedToCheckType; 1038 1039 return kCannotHandle; 1040 } 1041 1042 1043 inline bool 1044 IsSuperHandlerSignature(const char* signature) 1045 { 1046 return strcasecmp(signature, B_FILE_MIMETYPE) == 0; 1047 } 1048 1049 1050 enum { 1051 kDontMatch = 0, 1052 kMatchSupertype, 1053 kMatch 1054 }; 1055 1056 1057 static int32 1058 MatchMimeTypeString(/*const */BString* documentType, const char* handlerType) 1059 { 1060 // perform a mime type wildcard match 1061 // handler types of the form "text" 1062 // handle every handled type with same supertype, 1063 // for everything else a full string match is used 1064 1065 int32 supertypeOnlyLength = 0; 1066 const char* tmp = strstr(handlerType, "/"); 1067 1068 if (tmp == NULL) { 1069 // no subtype - supertype string only 1070 supertypeOnlyLength = (int32)strlen(handlerType); 1071 } 1072 1073 if (supertypeOnlyLength) { 1074 // compare just the supertype 1075 tmp = strstr(documentType->String(), "/"); 1076 if (tmp && (tmp - documentType->String() == supertypeOnlyLength)) { 1077 if (documentType->ICompare(handlerType, supertypeOnlyLength) == 0) 1078 return kMatchSupertype; 1079 else 1080 return kDontMatch; 1081 } 1082 } 1083 1084 if (documentType->ICompare(handlerType) == 0) 1085 return kMatch; 1086 1087 return kDontMatch; 1088 } 1089 1090 1091 int32 1092 Model::SupportsMimeType(const char* type, const BObjectList<BString>* list, 1093 bool exactReason) const 1094 { 1095 ASSERT((type == 0) != (list == 0)); 1096 // pass in one or the other 1097 1098 int32 result = kDoesNotSupportType; 1099 1100 BFile file(EntryRef(), O_RDONLY); 1101 BAppFileInfo handlerInfo(&file); 1102 1103 BMessage message; 1104 if (handlerInfo.GetSupportedTypes(&message) != B_OK) 1105 return kDoesNotSupportType; 1106 1107 for (int32 index = 0; ; index++) { 1108 // check if this model lists the type of dropped document as supported 1109 1110 const char* mimeSignature; 1111 ssize_t bufferLength; 1112 1113 if (message.FindData("types", 'CSTR', index, 1114 (const void**)&mimeSignature, &bufferLength)) { 1115 return result; 1116 } 1117 1118 if (IsSuperHandlerSignature(mimeSignature)) { 1119 if (!exactReason) 1120 return kSuperhandlerModel; 1121 1122 if (result == kDoesNotSupportType) 1123 result = kSuperhandlerModel; 1124 } 1125 1126 int32 match; 1127 1128 if (type != NULL || (list != NULL && list->IsEmpty())) { 1129 BString typeString(type); 1130 match = MatchMimeTypeString(&typeString, mimeSignature); 1131 } else { 1132 match = WhileEachListItem(const_cast<BObjectList<BString>*>(list), 1133 MatchMimeTypeString, mimeSignature); 1134 // const_cast shouldnt be here, have to have it until 1135 // MW cleans up 1136 } 1137 if (match == kMatch) 1138 // supports the actual type, it can't get any better 1139 return kModelSupportsType; 1140 else if (match == kMatchSupertype) { 1141 if (!exactReason) 1142 return kModelSupportsSupertype; 1143 1144 // we already know this model supports the file as a supertype, 1145 // now find out if it matches the type 1146 result = kModelSupportsSupertype; 1147 } 1148 } 1149 1150 return result; 1151 } 1152 1153 1154 bool 1155 Model::IsDropTargetForList(const BObjectList<BString>* list) const 1156 { 1157 switch (CanHandleDrops()) { 1158 case kCanHandle: 1159 return true; 1160 1161 case kCannotHandle: 1162 return false; 1163 1164 default: 1165 break; 1166 } 1167 1168 return SupportsMimeType(0, list) != kDoesNotSupportType; 1169 } 1170 1171 1172 bool 1173 Model::IsSuperHandler() const 1174 { 1175 ASSERT(CanHandleDrops() == kNeedToCheckType); 1176 1177 BFile file(EntryRef(), O_RDONLY); 1178 BAppFileInfo handlerInfo(&file); 1179 1180 BMessage message; 1181 if (handlerInfo.GetSupportedTypes(&message) != B_OK) 1182 return false; 1183 1184 for (int32 index = 0; ; index++) { 1185 const char* mimeSignature; 1186 ssize_t bufferLength; 1187 1188 if (message.FindData("types", 'CSTR', index, 1189 (const void**)&mimeSignature, &bufferLength)) { 1190 return false; 1191 } 1192 1193 if (IsSuperHandlerSignature(mimeSignature)) 1194 return true; 1195 } 1196 return false; 1197 } 1198 1199 1200 void 1201 Model::GetEntry(BEntry* entry) const 1202 { 1203 entry->SetTo(EntryRef()); 1204 } 1205 1206 1207 void 1208 Model::GetPath(BPath* path) const 1209 { 1210 BEntry entry(EntryRef()); 1211 entry.GetPath(path); 1212 } 1213 1214 1215 bool 1216 Model::Mimeset(bool force) 1217 { 1218 BString oldType = MimeType(); 1219 BPath path; 1220 GetPath(&path); 1221 1222 update_mime_info(path.Path(), 0, 1, force ? 2 : 0); 1223 ModelNodeLazyOpener opener(this); 1224 opener.OpenNode(); 1225 AttrChanged(NULL); 1226 1227 return !oldType.ICompare(MimeType()); 1228 } 1229 1230 1231 ssize_t 1232 Model::WriteAttr(const char* attr, type_code type, off_t offset, 1233 const void* buffer, size_t length) 1234 { 1235 BModelWriteOpener opener(this); 1236 if (!fNode) 1237 return 0; 1238 1239 ssize_t result = fNode->WriteAttr(attr, type, offset, buffer, length); 1240 return result; 1241 } 1242 1243 1244 ssize_t 1245 Model::WriteAttrKillForeign(const char* attr, const char* foreignAttr, 1246 type_code type, off_t offset, const void* buffer, size_t length) 1247 { 1248 BModelWriteOpener opener(this); 1249 if (!fNode) 1250 return 0; 1251 1252 ssize_t result = fNode->WriteAttr(attr, type, offset, buffer, length); 1253 if (result == (ssize_t)length) 1254 // nuke attribute in opposite endianness 1255 fNode->RemoveAttr(foreignAttr); 1256 return result; 1257 } 1258 1259 1260 status_t 1261 Model::GetLongVersionString(BString &result, version_kind kind) 1262 { 1263 BFile file(EntryRef(), O_RDONLY); 1264 status_t error = file.InitCheck(); 1265 if (error != B_OK) 1266 return error; 1267 1268 BAppFileInfo info(&file); 1269 error = info.InitCheck(); 1270 if (error != B_OK) 1271 return error; 1272 1273 version_info version; 1274 error = info.GetVersionInfo(&version, kind); 1275 if (error != B_OK) 1276 return error; 1277 1278 result = version.long_info; 1279 return B_OK; 1280 } 1281 1282 1283 status_t 1284 Model::GetVersionString(BString &result, version_kind kind) 1285 { 1286 BFile file(EntryRef(), O_RDONLY); 1287 status_t error = file.InitCheck(); 1288 if (error != B_OK) 1289 return error; 1290 1291 BAppFileInfo info(&file); 1292 error = info.InitCheck(); 1293 if (error != B_OK) 1294 return error; 1295 1296 version_info version; 1297 error = info.GetVersionInfo(&version, kind); 1298 if (error != B_OK) 1299 return error; 1300 1301 result.SetToFormat("%" B_PRId32 ".%" B_PRId32 ".%" B_PRId32, version.major, 1302 version.middle, version.minor); 1303 1304 return B_OK; 1305 } 1306 1307 #if DEBUG 1308 1309 void 1310 Model::PrintToStream(int32 level, bool deep) 1311 { 1312 PRINT(("model name %s, entry name %s, inode %" B_PRIdINO ", dev %" 1313 B_PRIdDEV ", directory inode %" B_PRIdINO "\n", 1314 Name() ? Name() : "**empty name**", 1315 EntryRef()->name ? EntryRef()->name : "**empty ref name**", 1316 NodeRef()->node, 1317 NodeRef()->device, 1318 EntryRef()->directory)); 1319 PRINT(("type %s \n", MimeType())); 1320 1321 PRINT(("model type: ")); 1322 switch (fBaseType) { 1323 case kPlainNode: 1324 PRINT(("plain\n")); 1325 break; 1326 1327 case kQueryNode: 1328 PRINT(("query\n")); 1329 break; 1330 1331 case kQueryTemplateNode: 1332 PRINT(("query template\n")); 1333 break; 1334 1335 case kExecutableNode: 1336 PRINT(("exe\n")); 1337 break; 1338 1339 case kDirectoryNode: 1340 case kTrashNode: 1341 case kDesktopNode: 1342 PRINT(("dir\n")); 1343 break; 1344 1345 case kLinkNode: 1346 PRINT(("link\n")); 1347 break; 1348 1349 case kRootNode: 1350 PRINT(("root\n")); 1351 break; 1352 1353 case kVolumeNode: 1354 PRINT(("volume, name %s\n", fVolumeName ? fVolumeName : "")); 1355 break; 1356 1357 case kVirtualDirectoryNode: 1358 PRINT(("virtual directory\n")); 1359 break; 1360 1361 default: 1362 PRINT(("unknown\n")); 1363 break; 1364 } 1365 1366 if (level < 1) 1367 return; 1368 1369 if (!IsVolume()) { 1370 PRINT(("preferred app %s\n", 1371 fPreferredAppName ? fPreferredAppName : "")); 1372 } 1373 1374 PRINT(("icon from: ")); 1375 switch (IconFrom()) { 1376 case kUnknownSource: 1377 PRINT(("unknown\n")); 1378 break; 1379 1380 case kUnknownNotFromNode: 1381 PRINT(("unknown but not from a node\n")); 1382 break; 1383 1384 case kTrackerDefault: 1385 PRINT(("tracker default\n")); 1386 break; 1387 1388 case kTrackerSupplied: 1389 PRINT(("tracker supplied\n")); 1390 break; 1391 1392 case kMetaMime: 1393 PRINT(("metamime\n")); 1394 break; 1395 1396 case kPreferredAppForType: 1397 PRINT(("preferred app for type\n")); 1398 break; 1399 1400 case kPreferredAppForNode: 1401 PRINT(("preferred app for node\n")); 1402 break; 1403 1404 case kNode: 1405 PRINT(("node\n")); 1406 break; 1407 1408 case kVolume: 1409 PRINT(("volume\n")); 1410 break; 1411 1412 default: 1413 break; 1414 } 1415 1416 PRINT(("model %s opened %s \n", !IsNodeOpen() ? "not " : "", 1417 IsNodeOpenForWriting() ? "for writing" : "")); 1418 1419 if (IsNodeOpen()) { 1420 node_ref nodeRef; 1421 fNode->GetNodeRef(&nodeRef); 1422 PRINT(("node ref of open Node %" B_PRIdINO " %" B_PRIdDEV "\n", 1423 nodeRef.node, nodeRef.device)); 1424 } 1425 1426 if (deep && IsSymLink()) { 1427 BEntry tmpEntry(EntryRef(), true); 1428 Model tmp(&tmpEntry); 1429 PRINT(("symlink to:\n")); 1430 tmp.PrintToStream(); 1431 } 1432 TrackIconSource(B_MINI_ICON); 1433 TrackIconSource(B_LARGE_ICON); 1434 } 1435 1436 1437 void 1438 Model::TrackIconSource(icon_size size) 1439 { 1440 PRINT(("tracking %s icon\n", size == B_LARGE_ICON ? "large" : "small")); 1441 BRect rect; 1442 if (size == B_MINI_ICON) 1443 rect.Set(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1); 1444 else 1445 rect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1); 1446 1447 BBitmap bitmap(rect, B_CMAP8); 1448 1449 BModelOpener opener(this); 1450 1451 if (Node() == NULL) { 1452 PRINT(("track icon error - no node\n")); 1453 return; 1454 } 1455 1456 if (IsSymLink()) { 1457 PRINT(("tracking symlink icon\n")); 1458 if (fLinkTo) { 1459 fLinkTo->TrackIconSource(size); 1460 return; 1461 } 1462 } 1463 1464 if (fBaseType == kVolumeNode) { 1465 BVolume volume(NodeRef()->device); 1466 status_t result = volume.GetIcon(&bitmap, size); 1467 PRINT(("getting icon from volume %s\n", strerror(result))); 1468 } else { 1469 BNodeInfo nodeInfo(Node()); 1470 1471 status_t err = nodeInfo.GetIcon(&bitmap, size); 1472 if (err == B_OK) { 1473 // file knew which icon to use, we are done 1474 PRINT(("track icon - got icon from file\n")); 1475 return; 1476 } 1477 1478 char preferredApp[B_MIME_TYPE_LENGTH]; 1479 err = nodeInfo.GetPreferredApp(preferredApp); 1480 if (err == B_OK && preferredApp[0]) { 1481 BMimeType preferredAppType(preferredApp); 1482 err = preferredAppType.GetIconForType(MimeType(), &bitmap, size); 1483 if (err == B_OK) { 1484 PRINT( 1485 ("track icon - got icon for type %s from preferred " 1486 "app %s for file\n", MimeType(), preferredApp)); 1487 return; 1488 } 1489 } 1490 1491 BMimeType mimeType(MimeType()); 1492 err = mimeType.GetIcon(&bitmap, size); 1493 if (err == B_OK) { 1494 // the system knew what icon to use for the type, we are done 1495 PRINT(("track icon - signature %s, got icon from system\n", 1496 MimeType())); 1497 return; 1498 } 1499 1500 err = mimeType.GetPreferredApp(preferredApp); 1501 if (err != B_OK) { 1502 // no preferred App for document, give up 1503 PRINT(("track icon - signature %s, no prefered app, error %s\n", 1504 MimeType(), strerror(err))); 1505 return; 1506 } 1507 1508 BMimeType preferredAppType(preferredApp); 1509 err = preferredAppType.GetIconForType(MimeType(), &bitmap, size); 1510 if (err == B_OK) { 1511 // the preferred app knew icon to use for the type, we are done 1512 PRINT( 1513 ("track icon - signature %s, got icon from preferred " 1514 "app %s\n", MimeType(), preferredApp)); 1515 return; 1516 } 1517 PRINT( 1518 ("track icon - signature %s, preferred app %s, no icon, " 1519 "error %s\n", MimeType(), preferredApp, strerror(err))); 1520 } 1521 } 1522 1523 #endif // DEBUG 1524 1525 #ifdef CHECK_OPEN_MODEL_LEAKS 1526 1527 namespace BPrivate { 1528 1529 #include <stdio.h> 1530 1531 void 1532 DumpOpenModels(bool extensive) 1533 { 1534 if (readOnlyOpenModelList) { 1535 int32 count = readOnlyOpenModelList->CountItems(); 1536 printf("%ld models open read-only:\n", count); 1537 printf("==========================\n"); 1538 for (int32 index = 0; index < count; index++) { 1539 if (extensive) { 1540 printf("---------------------------\n"); 1541 readOnlyOpenModelList->ItemAt(index)->PrintToStream(); 1542 } else 1543 printf("%s\n", readOnlyOpenModelList->ItemAt(index)->Name()); 1544 } 1545 } 1546 1547 if (writableOpenModelList) { 1548 int32 count = writableOpenModelList->CountItems(); 1549 printf("%ld models open writable:\n", count); 1550 printf("models open writable:\n"); 1551 printf("======================\n"); 1552 for (int32 index = 0; index < count; index++) { 1553 if (extensive) { 1554 printf("---------------------------\n"); 1555 writableOpenModelList->ItemAt(index)->PrintToStream(); 1556 } else 1557 printf("%s\n", writableOpenModelList->ItemAt(index)->Name()); 1558 } 1559 } 1560 } 1561 1562 1563 void 1564 InitOpenModelDumping() 1565 { 1566 readOnlyOpenModelList = 0; 1567 writableOpenModelList = 0; 1568 } 1569 1570 } // namespace BPrivate 1571 1572 #endif // CHECK_OPEN_MODEL_LEAKS 1573