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