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