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