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