xref: /haiku/src/kits/tracker/Model.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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 kPlainNode:
430 		case kExecutableNode:
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 kDirectoryNode:
441 		case kVolumeNode:
442 		case kRootNode:
443 		case kTrashNode:
444 		case kDesktopNode:
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 const Model*
812 Model::ResolveIfLink() const
813 {
814 	if (!IsSymLink())
815 		return this;
816 
817 	if (!fLinkTo)
818 		return this;
819 
820 	return fLinkTo;
821 }
822 
823 
824 Model*
825 Model::ResolveIfLink()
826 {
827 	if (!IsSymLink())
828 		return this;
829 
830 	if (!fLinkTo)
831 		return this;
832 
833 	return fLinkTo;
834 }
835 
836 
837 void
838 Model::SetLinkTo(Model* model)
839 {
840 	ASSERT(IsSymLink());
841 	ASSERT(!fLinkTo || (fLinkTo != model));
842 
843 	delete fLinkTo;
844 	fLinkTo = model;
845 }
846 
847 
848 //	#pragma mark - Node monitor updating methods
849 
850 
851 void
852 Model::UpdateEntryRef(const node_ref* dirNode, const char* name)
853 {
854 	if (IsVolume()) {
855 		if (fVolumeName != NULL)
856 			DeletePreferredAppVolumeNameLinkTo();
857 
858 		fVolumeName = strdup(name);
859 	}
860 
861 	fEntryRef.device = dirNode->device;
862 	fEntryRef.directory = dirNode->node;
863 
864 	if (fEntryRef.name != NULL && strcmp(fEntryRef.name, name) == 0)
865 		return;
866 
867 	fEntryRef.set_name(name);
868 }
869 
870 
871 status_t
872 Model::WatchVolumeAndMountPoint(uint32 , BHandler* target)
873 {
874 	ASSERT(IsVolume());
875 
876 	if (fEntryRef.name != NULL && fVolumeName != NULL
877 		&& strcmp(fEntryRef.name, "boot") == 0) {
878 		// watch mount point for boot volume
879 		BString bootMountPoint("/");
880 		bootMountPoint += fVolumeName;
881 		BEntry mountPointEntry(bootMountPoint.String());
882 		Model mountPointModel(&mountPointEntry);
883 
884 		TTracker::WatchNode(mountPointModel.NodeRef(),
885 			B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, target);
886 	}
887 
888 	return TTracker::WatchNode(NodeRef(),
889 		B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, target);
890 }
891 
892 
893 bool
894 Model::AttrChanged(const char* attrName)
895 {
896 	// called on an attribute changed node monitor
897 	// sync up cached values of mime type and preferred app and
898 	// return true if icon needs updating
899 
900 	ASSERT(IsNodeOpen());
901 	if (attrName != NULL
902 		&& (strcmp(attrName, kAttrIcon) == 0
903 			|| strcmp(attrName, kAttrMiniIcon) == 0
904 			|| strcmp(attrName, kAttrLargeIcon) == 0
905 			|| strcmp(attrName, kAttrThumbnail) == 0)) {
906 		return true;
907 	}
908 
909 	if (attrName == NULL
910 		|| strcmp(attrName, kAttrMIMEType) == 0
911 		|| strcmp(attrName, kAttrPreferredApp) == 0) {
912 		char type[B_MIME_TYPE_LENGTH];
913 		BNodeInfo info(fNode);
914 		if (info.GetType(type) != B_OK)
915 			fMimeType = "";
916 		else {
917 			// node has a specific mime type
918 			fMimeType = type;
919 			if (!IsVolume() && !IsSymLink()
920 				&& info.GetPreferredApp(type) == B_OK) {
921 				SetPreferredAppSignature(type);
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 
1283 status_t
1284 Model::GetVersionString(BString &result, version_kind kind)
1285 {
1286 	BFile file(EntryRef(), O_RDONLY);
1287 	status_t error = file.InitCheck();
1288 	if (error != B_OK)
1289 		return error;
1290 
1291 	BAppFileInfo info(&file);
1292 	error = info.InitCheck();
1293 	if (error != B_OK)
1294 		return error;
1295 
1296 	version_info version;
1297 	error = info.GetVersionInfo(&version, kind);
1298 	if (error != B_OK)
1299 		return error;
1300 
1301 	result.SetToFormat("%" B_PRId32 ".%" B_PRId32 ".%" B_PRId32, version.major,
1302 		version.middle, version.minor);
1303 
1304 	return B_OK;
1305 }
1306 
1307 #if DEBUG
1308 
1309 void
1310 Model::PrintToStream(int32 level, bool deep)
1311 {
1312 	PRINT(("model name %s, entry name %s, inode %" B_PRIdINO ", dev %"
1313 		B_PRIdDEV ", directory inode %" B_PRIdINO "\n",
1314 		Name() ? Name() : "**empty name**",
1315 		EntryRef()->name ? EntryRef()->name : "**empty ref name**",
1316 		NodeRef()->node,
1317 		NodeRef()->device,
1318 		EntryRef()->directory));
1319 	PRINT(("type %s \n", MimeType()));
1320 
1321 	PRINT(("model type: "));
1322 	switch (fBaseType) {
1323 		case kPlainNode:
1324 			PRINT(("plain\n"));
1325 			break;
1326 
1327 		case kQueryNode:
1328 			PRINT(("query\n"));
1329 			break;
1330 
1331 		case kQueryTemplateNode:
1332 			PRINT(("query template\n"));
1333 			break;
1334 
1335 		case kExecutableNode:
1336 			PRINT(("exe\n"));
1337 			break;
1338 
1339 		case kDirectoryNode:
1340 		case kTrashNode:
1341 		case kDesktopNode:
1342 			PRINT(("dir\n"));
1343 			break;
1344 
1345 		case kLinkNode:
1346 			PRINT(("link\n"));
1347 			break;
1348 
1349 		case kRootNode:
1350 			PRINT(("root\n"));
1351 			break;
1352 
1353 		case kVolumeNode:
1354 			PRINT(("volume, name %s\n", fVolumeName ? fVolumeName : ""));
1355 			break;
1356 
1357 		case kVirtualDirectoryNode:
1358 			PRINT(("virtual directory\n"));
1359 			break;
1360 
1361 		default:
1362 			PRINT(("unknown\n"));
1363 			break;
1364 	}
1365 
1366 	if (level < 1)
1367 		return;
1368 
1369 	if (!IsVolume()) {
1370 		PRINT(("preferred app %s\n",
1371 			fPreferredAppName ? fPreferredAppName : ""));
1372 	}
1373 
1374 	PRINT(("icon from: "));
1375 	switch (IconFrom()) {
1376 		case kUnknownSource:
1377 			PRINT(("unknown\n"));
1378 			break;
1379 
1380 		case kUnknownNotFromNode:
1381 			PRINT(("unknown but not from a node\n"));
1382 			break;
1383 
1384 		case kTrackerDefault:
1385 			PRINT(("tracker default\n"));
1386 			break;
1387 
1388 		case kTrackerSupplied:
1389 			PRINT(("tracker supplied\n"));
1390 			break;
1391 
1392 		case kMetaMime:
1393 			PRINT(("metamime\n"));
1394 			break;
1395 
1396 		case kPreferredAppForType:
1397 			PRINT(("preferred app for type\n"));
1398 			break;
1399 
1400 		case kPreferredAppForNode:
1401 			PRINT(("preferred app for node\n"));
1402 			break;
1403 
1404 		case kNode:
1405 			PRINT(("node\n"));
1406 			break;
1407 
1408 		case kVolume:
1409 			PRINT(("volume\n"));
1410 			break;
1411 
1412 		default:
1413 			break;
1414 	}
1415 
1416 	PRINT(("model %s opened %s \n", !IsNodeOpen() ? "not " : "",
1417 		IsNodeOpenForWriting() ? "for writing" : ""));
1418 
1419 	if (IsNodeOpen()) {
1420 		node_ref nodeRef;
1421 		fNode->GetNodeRef(&nodeRef);
1422 		PRINT(("node ref of open Node %" B_PRIdINO " %" B_PRIdDEV "\n",
1423 			nodeRef.node, nodeRef.device));
1424 	}
1425 
1426 	if (deep && IsSymLink()) {
1427 		BEntry tmpEntry(EntryRef(), true);
1428 		Model tmp(&tmpEntry);
1429 		PRINT(("symlink to:\n"));
1430 		tmp.PrintToStream();
1431 	}
1432 	TrackIconSource(B_MINI_ICON);
1433 	TrackIconSource(B_LARGE_ICON);
1434 }
1435 
1436 
1437 void
1438 Model::TrackIconSource(icon_size size)
1439 {
1440 	PRINT(("tracking %s icon\n", size == B_LARGE_ICON ? "large" : "small"));
1441 	BRect rect;
1442 	if (size == B_MINI_ICON)
1443 		rect.Set(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
1444 	else
1445 		rect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1);
1446 
1447 	BBitmap bitmap(rect, B_CMAP8);
1448 
1449 	BModelOpener opener(this);
1450 
1451 	if (Node() == NULL) {
1452 		PRINT(("track icon error - no node\n"));
1453 		return;
1454 	}
1455 
1456 	if (IsSymLink()) {
1457 		PRINT(("tracking symlink icon\n"));
1458 		if (fLinkTo) {
1459 			fLinkTo->TrackIconSource(size);
1460 			return;
1461 		}
1462 	}
1463 
1464 	if (fBaseType == kVolumeNode) {
1465 		BVolume volume(NodeRef()->device);
1466 		status_t result = volume.GetIcon(&bitmap, size);
1467 		PRINT(("getting icon from volume %s\n", strerror(result)));
1468 	} else {
1469 		BNodeInfo nodeInfo(Node());
1470 
1471 		status_t err = nodeInfo.GetIcon(&bitmap, size);
1472 		if (err == B_OK) {
1473 			// file knew which icon to use, we are done
1474 			PRINT(("track icon - got icon from file\n"));
1475 			return;
1476 		}
1477 
1478 		char preferredApp[B_MIME_TYPE_LENGTH];
1479 		err = nodeInfo.GetPreferredApp(preferredApp);
1480 		if (err == B_OK && preferredApp[0]) {
1481 			BMimeType preferredAppType(preferredApp);
1482 			err = preferredAppType.GetIconForType(MimeType(), &bitmap, size);
1483 			if (err == B_OK) {
1484 				PRINT(
1485 					("track icon - got icon for type %s from preferred "
1486 					 "app %s for file\n", MimeType(), preferredApp));
1487 				return;
1488 			}
1489 		}
1490 
1491 		BMimeType mimeType(MimeType());
1492 		err = mimeType.GetIcon(&bitmap, size);
1493 		if (err == B_OK) {
1494 			// the system knew what icon to use for the type, we are done
1495 			PRINT(("track icon - signature %s, got icon from system\n",
1496 				MimeType()));
1497 			return;
1498 		}
1499 
1500 		err = mimeType.GetPreferredApp(preferredApp);
1501 		if (err != B_OK) {
1502 			// no preferred App for document, give up
1503 			PRINT(("track icon - signature %s, no prefered app, error %s\n",
1504 				MimeType(), strerror(err)));
1505 			return;
1506 		}
1507 
1508 		BMimeType preferredAppType(preferredApp);
1509 		err = preferredAppType.GetIconForType(MimeType(), &bitmap, size);
1510 		if (err == B_OK) {
1511 			// the preferred app knew icon to use for the type, we are done
1512 			PRINT(
1513 				("track icon - signature %s, got icon from preferred "
1514 				 "app %s\n", MimeType(), preferredApp));
1515 			return;
1516 		}
1517 		PRINT(
1518 			("track icon - signature %s, preferred app %s, no icon, "
1519 			 "error %s\n", MimeType(), preferredApp, strerror(err)));
1520 	}
1521 }
1522 
1523 #endif	// DEBUG
1524 
1525 #ifdef CHECK_OPEN_MODEL_LEAKS
1526 
1527 namespace BPrivate {
1528 
1529 #include <stdio.h>
1530 
1531 void
1532 DumpOpenModels(bool extensive)
1533 {
1534 	if (readOnlyOpenModelList) {
1535 		int32 count = readOnlyOpenModelList->CountItems();
1536 		printf("%ld models open read-only:\n", count);
1537 		printf("==========================\n");
1538 		for (int32 index = 0; index < count; index++) {
1539 			if (extensive) {
1540 				printf("---------------------------\n");
1541 				readOnlyOpenModelList->ItemAt(index)->PrintToStream();
1542 			} else
1543 				printf("%s\n", readOnlyOpenModelList->ItemAt(index)->Name());
1544 		}
1545 	}
1546 
1547 	if (writableOpenModelList) {
1548 		int32 count = writableOpenModelList->CountItems();
1549 		printf("%ld models open writable:\n", count);
1550 		printf("models open writable:\n");
1551 		printf("======================\n");
1552 		for (int32 index = 0; index < count; index++) {
1553 			if (extensive) {
1554 				printf("---------------------------\n");
1555 				writableOpenModelList->ItemAt(index)->PrintToStream();
1556 			} else
1557 				printf("%s\n", writableOpenModelList->ItemAt(index)->Name());
1558 		}
1559 	}
1560 }
1561 
1562 
1563 void
1564 InitOpenModelDumping()
1565 {
1566 	readOnlyOpenModelList = 0;
1567 	writableOpenModelList = 0;
1568 }
1569 
1570 }	// namespace BPrivate
1571 
1572 #endif	// CHECK_OPEN_MODEL_LEAKS
1573