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