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