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