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