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