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 kExecutableNode: 430 case kPlainNode: 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 kDesktopNode: 441 case kDirectoryNode: 442 case kRootNode: 443 case kTrashNode: 444 case kVolumeNode: 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 bool 812 Model::IsPrintersDir() const 813 { 814 BEntry entry(EntryRef()); 815 return FSIsPrintersDir(&entry); 816 } 817 818 819 bool 820 Model::InRoot() const 821 { 822 return FSInRootDir(EntryRef()); 823 } 824 825 826 bool 827 Model::InTrash() const 828 { 829 return FSInTrashDir(EntryRef()); 830 } 831 832 833 const Model* 834 Model::ResolveIfLink() const 835 { 836 if (!IsSymLink()) 837 return this; 838 839 if (!fLinkTo) 840 return this; 841 842 return fLinkTo; 843 } 844 845 846 Model* 847 Model::ResolveIfLink() 848 { 849 if (!IsSymLink()) 850 return this; 851 852 if (!fLinkTo) 853 return this; 854 855 return fLinkTo; 856 } 857 858 859 void 860 Model::SetLinkTo(Model* model) 861 { 862 ASSERT(IsSymLink()); 863 ASSERT(!fLinkTo || (fLinkTo != model)); 864 865 delete fLinkTo; 866 fLinkTo = model; 867 } 868 869 870 // #pragma mark - Node monitor updating methods 871 872 873 void 874 Model::UpdateEntryRef(const node_ref* dirNode, const char* name) 875 { 876 if (IsVolume()) { 877 if (fVolumeName != NULL) 878 DeletePreferredAppVolumeNameLinkTo(); 879 880 fVolumeName = strdup(name); 881 } 882 883 fEntryRef.device = dirNode->device; 884 fEntryRef.directory = dirNode->node; 885 886 if (fEntryRef.name != NULL && strcmp(fEntryRef.name, name) == 0) 887 return; 888 889 fEntryRef.set_name(name); 890 } 891 892 893 status_t 894 Model::WatchVolumeAndMountPoint(uint32 , BHandler* target) 895 { 896 ASSERT(IsVolume()); 897 898 if (fEntryRef.name != NULL && fVolumeName != NULL 899 && strcmp(fEntryRef.name, "boot") == 0) { 900 // watch mount point for boot volume 901 BString bootMountPoint("/"); 902 bootMountPoint += fVolumeName; 903 BEntry mountPointEntry(bootMountPoint.String()); 904 Model mountPointModel(&mountPointEntry); 905 906 TTracker::WatchNode(mountPointModel.NodeRef(), 907 B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, target); 908 } 909 910 return TTracker::WatchNode(NodeRef(), 911 B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, target); 912 } 913 914 915 bool 916 Model::AttrChanged(const char* attrName) 917 { 918 // called on an attribute changed node monitor 919 // sync up cached values of mime type and preferred app and 920 // return true if icon needs updating 921 922 ASSERT(IsNodeOpen()); 923 if (attrName != NULL 924 && (strcmp(attrName, kAttrIcon) == 0 925 || strcmp(attrName, kAttrMiniIcon) == 0 926 || strcmp(attrName, kAttrLargeIcon) == 0 927 || strcmp(attrName, kAttrThumbnail) == 0)) { 928 return true; 929 } 930 931 if (attrName == NULL 932 || strcmp(attrName, kAttrMIMEType) == 0 933 || strcmp(attrName, kAttrPreferredApp) == 0) { 934 char type[B_MIME_TYPE_LENGTH]; 935 BNodeInfo info(fNode); 936 if (info.GetType(type) != B_OK) 937 fMimeType = ""; 938 else { 939 // node has a specific mime type 940 fMimeType = type; 941 if (!IsVolume() && !IsSymLink() 942 && info.GetPreferredApp(type) == B_OK) { 943 SetPreferredAppSignature(type); 944 } 945 } 946 947 #if xDEBUG 948 if (fIconFrom != kNode) { 949 PRINT(("%s, %s:updating icon because file type changed\n", 950 Name(), attrName != NULL ? attrName : "")); 951 } else { 952 PRINT(("Not updating icon even though type changed " 953 "because icon is from node.\n")); 954 } 955 #endif 956 957 return fIconFrom != kNode; 958 // update icon unless it is coming from a node 959 } 960 961 return attrName == NULL; 962 } 963 964 965 bool 966 Model::StatChanged() 967 { 968 ASSERT(IsNodeOpen()); 969 mode_t oldMode = fStatBuf.st_mode; 970 fStatus = fNode->GetStat(&fStatBuf); 971 972 if (oldMode != fStatBuf.st_mode) { 973 bool forWriting = IsNodeOpenForWriting(); 974 CloseNode(); 975 //SetupBaseType(); 976 // the node type can't change with a stat update... 977 OpenNodeCommon(forWriting); 978 return true; 979 } 980 981 return false; 982 } 983 984 985 // #pragma mark - Mime handling methods 986 987 988 bool 989 Model::IsDropTarget(const Model* forDocument, bool traverse) const 990 { 991 switch (CanHandleDrops()) { 992 case kCanHandle: 993 return true; 994 995 case kCannotHandle: 996 return false; 997 998 default: 999 break; 1000 } 1001 1002 if (forDocument == NULL) 1003 return true; 1004 1005 if (traverse) { 1006 BEntry entry(forDocument->EntryRef(), true); 1007 if (entry.InitCheck() != B_OK) 1008 return false; 1009 1010 BFile file(&entry, O_RDONLY); 1011 BNodeInfo mime(&file); 1012 1013 if (mime.InitCheck() != B_OK) 1014 return false; 1015 1016 char mimeType[B_MIME_TYPE_LENGTH]; 1017 mime.GetType(mimeType); 1018 1019 return SupportsMimeType(mimeType, 0) != kDoesNotSupportType; 1020 } 1021 1022 // do some mime-based matching 1023 const char* documentMimeType = forDocument->MimeType(); 1024 if (documentMimeType == NULL) 1025 return false; 1026 1027 return SupportsMimeType(documentMimeType, 0) != kDoesNotSupportType; 1028 } 1029 1030 1031 Model::CanHandleResult 1032 Model::CanHandleDrops() const 1033 { 1034 if (IsDirectory() || IsVirtualDirectory()) { 1035 // directories take anything 1036 // resolve permissions here 1037 return kCanHandle; 1038 } 1039 1040 if (IsSymLink()) { 1041 // descend into symlink and try again on it's target 1042 1043 BEntry entry(&fEntryRef, true); 1044 if (entry.InitCheck() != B_OK) 1045 return kCannotHandle; 1046 1047 if (entry == BEntry(EntryRef())) 1048 // self-referencing link, avoid infinite recursion 1049 return kCannotHandle; 1050 1051 Model model(&entry); 1052 if (model.InitCheck() != B_OK) 1053 return kCannotHandle; 1054 1055 return model.CanHandleDrops(); 1056 } 1057 1058 if (IsExecutable()) 1059 return kNeedToCheckType; 1060 1061 return kCannotHandle; 1062 } 1063 1064 1065 inline bool 1066 IsSuperHandlerSignature(const char* signature) 1067 { 1068 return strcasecmp(signature, B_FILE_MIMETYPE) == 0; 1069 } 1070 1071 1072 enum { 1073 kDontMatch = 0, 1074 kMatchSupertype, 1075 kMatch 1076 }; 1077 1078 1079 static int32 1080 MatchMimeTypeString(/*const */BString* documentType, const char* handlerType) 1081 { 1082 // perform a mime type wildcard match 1083 // handler types of the form "text" 1084 // handle every handled type with same supertype, 1085 // for everything else a full string match is used 1086 1087 int32 supertypeOnlyLength = 0; 1088 const char* tmp = strstr(handlerType, "/"); 1089 1090 if (tmp == NULL) { 1091 // no subtype - supertype string only 1092 supertypeOnlyLength = (int32)strlen(handlerType); 1093 } 1094 1095 if (supertypeOnlyLength) { 1096 // compare just the supertype 1097 tmp = strstr(documentType->String(), "/"); 1098 if (tmp && (tmp - documentType->String() == supertypeOnlyLength)) { 1099 if (documentType->ICompare(handlerType, supertypeOnlyLength) == 0) 1100 return kMatchSupertype; 1101 else 1102 return kDontMatch; 1103 } 1104 } 1105 1106 if (documentType->ICompare(handlerType) == 0) 1107 return kMatch; 1108 1109 return kDontMatch; 1110 } 1111 1112 1113 int32 1114 Model::SupportsMimeType(const char* type, const BObjectList<BString>* list, 1115 bool exactReason) const 1116 { 1117 ASSERT((type == 0) != (list == 0)); 1118 // pass in one or the other 1119 1120 int32 result = kDoesNotSupportType; 1121 1122 BFile file(EntryRef(), O_RDONLY); 1123 BAppFileInfo handlerInfo(&file); 1124 1125 BMessage message; 1126 if (handlerInfo.GetSupportedTypes(&message) != B_OK) 1127 return kDoesNotSupportType; 1128 1129 for (int32 index = 0; ; index++) { 1130 // check if this model lists the type of dropped document as supported 1131 1132 const char* mimeSignature; 1133 ssize_t bufferLength; 1134 1135 if (message.FindData("types", 'CSTR', index, 1136 (const void**)&mimeSignature, &bufferLength)) { 1137 return result; 1138 } 1139 1140 if (IsSuperHandlerSignature(mimeSignature)) { 1141 if (!exactReason) 1142 return kSuperhandlerModel; 1143 1144 if (result == kDoesNotSupportType) 1145 result = kSuperhandlerModel; 1146 } 1147 1148 int32 match; 1149 1150 if (type != NULL || (list != NULL && list->IsEmpty())) { 1151 BString typeString(type); 1152 match = MatchMimeTypeString(&typeString, mimeSignature); 1153 } else { 1154 match = WhileEachListItem(const_cast<BObjectList<BString>*>(list), 1155 MatchMimeTypeString, mimeSignature); 1156 // const_cast shouldnt be here, have to have it until 1157 // MW cleans up 1158 } 1159 if (match == kMatch) 1160 // supports the actual type, it can't get any better 1161 return kModelSupportsType; 1162 else if (match == kMatchSupertype) { 1163 if (!exactReason) 1164 return kModelSupportsSupertype; 1165 1166 // we already know this model supports the file as a supertype, 1167 // now find out if it matches the type 1168 result = kModelSupportsSupertype; 1169 } 1170 } 1171 1172 return result; 1173 } 1174 1175 1176 bool 1177 Model::IsDropTargetForList(const BObjectList<BString>* list) const 1178 { 1179 switch (CanHandleDrops()) { 1180 case kCanHandle: 1181 return true; 1182 1183 case kCannotHandle: 1184 return false; 1185 1186 default: 1187 break; 1188 } 1189 1190 return SupportsMimeType(0, list) != kDoesNotSupportType; 1191 } 1192 1193 1194 bool 1195 Model::IsSuperHandler() const 1196 { 1197 ASSERT(CanHandleDrops() == kNeedToCheckType); 1198 1199 BFile file(EntryRef(), O_RDONLY); 1200 BAppFileInfo handlerInfo(&file); 1201 1202 BMessage message; 1203 if (handlerInfo.GetSupportedTypes(&message) != B_OK) 1204 return false; 1205 1206 for (int32 index = 0; ; index++) { 1207 const char* mimeSignature; 1208 ssize_t bufferLength; 1209 1210 if (message.FindData("types", 'CSTR', index, 1211 (const void**)&mimeSignature, &bufferLength)) { 1212 return false; 1213 } 1214 1215 if (IsSuperHandlerSignature(mimeSignature)) 1216 return true; 1217 } 1218 return false; 1219 } 1220 1221 1222 void 1223 Model::GetEntry(BEntry* entry) const 1224 { 1225 entry->SetTo(EntryRef()); 1226 } 1227 1228 1229 void 1230 Model::GetPath(BPath* path) const 1231 { 1232 BEntry entry(EntryRef()); 1233 entry.GetPath(path); 1234 } 1235 1236 1237 bool 1238 Model::Mimeset(bool force) 1239 { 1240 BString oldType = MimeType(); 1241 BPath path; 1242 GetPath(&path); 1243 1244 update_mime_info(path.Path(), 0, 1, force ? 2 : 0); 1245 ModelNodeLazyOpener opener(this); 1246 opener.OpenNode(); 1247 AttrChanged(NULL); 1248 1249 return !oldType.ICompare(MimeType()); 1250 } 1251 1252 1253 ssize_t 1254 Model::WriteAttr(const char* attr, type_code type, off_t offset, 1255 const void* buffer, size_t length) 1256 { 1257 BModelWriteOpener opener(this); 1258 if (!fNode) 1259 return 0; 1260 1261 ssize_t result = fNode->WriteAttr(attr, type, offset, buffer, length); 1262 return result; 1263 } 1264 1265 1266 ssize_t 1267 Model::WriteAttrKillForeign(const char* attr, const char* foreignAttr, 1268 type_code type, off_t offset, const void* buffer, size_t length) 1269 { 1270 BModelWriteOpener opener(this); 1271 if (!fNode) 1272 return 0; 1273 1274 ssize_t result = fNode->WriteAttr(attr, type, offset, buffer, length); 1275 if (result == (ssize_t)length) 1276 // nuke attribute in opposite endianness 1277 fNode->RemoveAttr(foreignAttr); 1278 return result; 1279 } 1280 1281 1282 status_t 1283 Model::GetLongVersionString(BString &result, version_kind kind) 1284 { 1285 BFile file(EntryRef(), O_RDONLY); 1286 status_t error = file.InitCheck(); 1287 if (error != B_OK) 1288 return error; 1289 1290 BAppFileInfo info(&file); 1291 error = info.InitCheck(); 1292 if (error != B_OK) 1293 return error; 1294 1295 version_info version; 1296 error = info.GetVersionInfo(&version, kind); 1297 if (error != B_OK) 1298 return error; 1299 1300 result = version.long_info; 1301 return B_OK; 1302 } 1303 1304 1305 status_t 1306 Model::GetVersionString(BString &result, version_kind kind) 1307 { 1308 BFile file(EntryRef(), O_RDONLY); 1309 status_t error = file.InitCheck(); 1310 if (error != B_OK) 1311 return error; 1312 1313 BAppFileInfo info(&file); 1314 error = info.InitCheck(); 1315 if (error != B_OK) 1316 return error; 1317 1318 version_info version; 1319 error = info.GetVersionInfo(&version, kind); 1320 if (error != B_OK) 1321 return error; 1322 1323 result.SetToFormat("%" B_PRId32 ".%" B_PRId32 ".%" B_PRId32, version.major, 1324 version.middle, version.minor); 1325 1326 return B_OK; 1327 } 1328 1329 #if DEBUG 1330 1331 void 1332 Model::PrintToStream(int32 level, bool deep) 1333 { 1334 PRINT(("model name %s, entry name %s, inode %" B_PRIdINO ", dev %" 1335 B_PRIdDEV ", directory inode %" B_PRIdINO "\n", 1336 Name() ? Name() : "**empty name**", 1337 EntryRef()->name ? EntryRef()->name : "**empty ref name**", 1338 NodeRef()->node, 1339 NodeRef()->device, 1340 EntryRef()->directory)); 1341 PRINT(("type %s \n", MimeType())); 1342 1343 PRINT(("model type: ")); 1344 switch (fBaseType) { 1345 case kPlainNode: 1346 PRINT(("plain\n")); 1347 break; 1348 1349 case kQueryNode: 1350 PRINT(("query\n")); 1351 break; 1352 1353 case kQueryTemplateNode: 1354 PRINT(("query template\n")); 1355 break; 1356 1357 case kExecutableNode: 1358 PRINT(("exe\n")); 1359 break; 1360 1361 case kDirectoryNode: 1362 case kTrashNode: 1363 case kDesktopNode: 1364 PRINT(("dir\n")); 1365 break; 1366 1367 case kLinkNode: 1368 PRINT(("link\n")); 1369 break; 1370 1371 case kRootNode: 1372 PRINT(("root\n")); 1373 break; 1374 1375 case kVolumeNode: 1376 PRINT(("volume, name %s\n", fVolumeName ? fVolumeName : "")); 1377 break; 1378 1379 case kVirtualDirectoryNode: 1380 PRINT(("virtual directory\n")); 1381 break; 1382 1383 default: 1384 PRINT(("unknown\n")); 1385 break; 1386 } 1387 1388 if (level < 1) 1389 return; 1390 1391 if (!IsVolume()) { 1392 PRINT(("preferred app %s\n", 1393 fPreferredAppName ? fPreferredAppName : "")); 1394 } 1395 1396 PRINT(("icon from: ")); 1397 switch (IconFrom()) { 1398 case kUnknownSource: 1399 PRINT(("unknown\n")); 1400 break; 1401 1402 case kUnknownNotFromNode: 1403 PRINT(("unknown but not from a node\n")); 1404 break; 1405 1406 case kTrackerDefault: 1407 PRINT(("tracker default\n")); 1408 break; 1409 1410 case kTrackerSupplied: 1411 PRINT(("tracker supplied\n")); 1412 break; 1413 1414 case kMetaMime: 1415 PRINT(("metamime\n")); 1416 break; 1417 1418 case kPreferredAppForType: 1419 PRINT(("preferred app for type\n")); 1420 break; 1421 1422 case kPreferredAppForNode: 1423 PRINT(("preferred app for node\n")); 1424 break; 1425 1426 case kNode: 1427 PRINT(("node\n")); 1428 break; 1429 1430 case kVolume: 1431 PRINT(("volume\n")); 1432 break; 1433 1434 default: 1435 break; 1436 } 1437 1438 PRINT(("model %s opened %s \n", !IsNodeOpen() ? "not " : "", 1439 IsNodeOpenForWriting() ? "for writing" : "")); 1440 1441 if (IsNodeOpen()) { 1442 node_ref nodeRef; 1443 fNode->GetNodeRef(&nodeRef); 1444 PRINT(("node ref of open Node %" B_PRIdINO " %" B_PRIdDEV "\n", 1445 nodeRef.node, nodeRef.device)); 1446 } 1447 1448 if (deep && IsSymLink()) { 1449 BEntry tmpEntry(EntryRef(), true); 1450 Model tmp(&tmpEntry); 1451 PRINT(("symlink to:\n")); 1452 tmp.PrintToStream(); 1453 } 1454 TrackIconSource(B_MINI_ICON); 1455 TrackIconSource(B_LARGE_ICON); 1456 } 1457 1458 1459 void 1460 Model::TrackIconSource(icon_size size) 1461 { 1462 PRINT(("tracking %s icon\n", size == B_LARGE_ICON ? "large" : "small")); 1463 BRect rect; 1464 if (size == B_MINI_ICON) 1465 rect.Set(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1); 1466 else 1467 rect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1); 1468 1469 BBitmap bitmap(rect, B_CMAP8); 1470 1471 BModelOpener opener(this); 1472 1473 if (Node() == NULL) { 1474 PRINT(("track icon error - no node\n")); 1475 return; 1476 } 1477 1478 if (IsSymLink()) { 1479 PRINT(("tracking symlink icon\n")); 1480 if (fLinkTo) { 1481 fLinkTo->TrackIconSource(size); 1482 return; 1483 } 1484 } 1485 1486 if (fBaseType == kVolumeNode) { 1487 BVolume volume(NodeRef()->device); 1488 status_t result = volume.GetIcon(&bitmap, size); 1489 PRINT(("getting icon from volume %s\n", strerror(result))); 1490 } else { 1491 BNodeInfo nodeInfo(Node()); 1492 1493 status_t err = nodeInfo.GetIcon(&bitmap, size); 1494 if (err == B_OK) { 1495 // file knew which icon to use, we are done 1496 PRINT(("track icon - got icon from file\n")); 1497 return; 1498 } 1499 1500 char preferredApp[B_MIME_TYPE_LENGTH]; 1501 err = nodeInfo.GetPreferredApp(preferredApp); 1502 if (err == B_OK && preferredApp[0]) { 1503 BMimeType preferredAppType(preferredApp); 1504 err = preferredAppType.GetIconForType(MimeType(), &bitmap, size); 1505 if (err == B_OK) { 1506 PRINT( 1507 ("track icon - got icon for type %s from preferred " 1508 "app %s for file\n", MimeType(), preferredApp)); 1509 return; 1510 } 1511 } 1512 1513 BMimeType mimeType(MimeType()); 1514 err = mimeType.GetIcon(&bitmap, size); 1515 if (err == B_OK) { 1516 // the system knew what icon to use for the type, we are done 1517 PRINT(("track icon - signature %s, got icon from system\n", 1518 MimeType())); 1519 return; 1520 } 1521 1522 err = mimeType.GetPreferredApp(preferredApp); 1523 if (err != B_OK) { 1524 // no preferred App for document, give up 1525 PRINT(("track icon - signature %s, no prefered app, error %s\n", 1526 MimeType(), strerror(err))); 1527 return; 1528 } 1529 1530 BMimeType preferredAppType(preferredApp); 1531 err = preferredAppType.GetIconForType(MimeType(), &bitmap, size); 1532 if (err == B_OK) { 1533 // the preferred app knew icon to use for the type, we are done 1534 PRINT( 1535 ("track icon - signature %s, got icon from preferred " 1536 "app %s\n", MimeType(), preferredApp)); 1537 return; 1538 } 1539 PRINT( 1540 ("track icon - signature %s, preferred app %s, no icon, " 1541 "error %s\n", MimeType(), preferredApp, strerror(err))); 1542 } 1543 } 1544 1545 #endif // DEBUG 1546 1547 #ifdef CHECK_OPEN_MODEL_LEAKS 1548 1549 namespace BPrivate { 1550 1551 #include <stdio.h> 1552 1553 void 1554 DumpOpenModels(bool extensive) 1555 { 1556 if (readOnlyOpenModelList) { 1557 int32 count = readOnlyOpenModelList->CountItems(); 1558 printf("%ld models open read-only:\n", count); 1559 printf("==========================\n"); 1560 for (int32 index = 0; index < count; index++) { 1561 if (extensive) { 1562 printf("---------------------------\n"); 1563 readOnlyOpenModelList->ItemAt(index)->PrintToStream(); 1564 } else 1565 printf("%s\n", readOnlyOpenModelList->ItemAt(index)->Name()); 1566 } 1567 } 1568 1569 if (writableOpenModelList) { 1570 int32 count = writableOpenModelList->CountItems(); 1571 printf("%ld models open writable:\n", count); 1572 printf("models open writable:\n"); 1573 printf("======================\n"); 1574 for (int32 index = 0; index < count; index++) { 1575 if (extensive) { 1576 printf("---------------------------\n"); 1577 writableOpenModelList->ItemAt(index)->PrintToStream(); 1578 } else 1579 printf("%s\n", writableOpenModelList->ItemAt(index)->Name()); 1580 } 1581 } 1582 } 1583 1584 1585 void 1586 InitOpenModelDumping() 1587 { 1588 readOnlyOpenModelList = 0; 1589 writableOpenModelList = 0; 1590 } 1591 1592 } // namespace BPrivate 1593 1594 #endif // CHECK_OPEN_MODEL_LEAKS 1595