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