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