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