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