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