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