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