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