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
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
80BObjectList<Model>* writableOpenModelList = NULL;
81BObjectList<Model>* readOnlyOpenModelList = NULL;
82#endif
83
84
85static bool
86CheckNodeIconHint(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
107Model::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
121Model::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
152Model::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
165Model::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
177Model::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
194void
195Model::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
211Model::~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
236status_t
237Model::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
262status_t
263Model::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
294status_t
295Model::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
329status_t
330Model::InitCheck() const
331{
332	return fStatus;
333}
334
335
336int
337Model::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
361const char*
362Model::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
394status_t
395Model::OpenNode(bool writable)
396{
397	if (IsNodeOpen() && (writable == IsNodeOpenForWriting()))
398		return B_OK;
399
400	OpenNodeCommon(writable);
401
402	return fStatus;
403}
404
405
406status_t
407Model::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
428status_t
429Model::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
530void
531Model::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
550bool
551Model::IsNodeOpen() const
552{
553	return fNode != NULL;
554}
555
556
557
558bool
559Model::IsNodeOpenForWriting() const
560{
561	return fNode != NULL && fWritable;
562}
563
564
565void
566Model::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
597void
598Model::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
611void
612Model::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
741void
742Model::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
766const char*
767Model::PreferredAppSignature() const
768{
769	if (IsVolume() || IsSymLink())
770		return "";
771
772	return fPreferredAppName ? fPreferredAppName : "";
773}
774
775
776void
777Model::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
791const Model*
792Model::ResolveIfLink() const
793{
794	if (!IsSymLink())
795		return this;
796
797	if (!fLinkTo)
798		return this;
799
800	return fLinkTo;
801}
802
803
804Model*
805Model::ResolveIfLink()
806{
807	if (!IsSymLink())
808		return this;
809
810	if (!fLinkTo)
811		return this;
812
813	return fLinkTo;
814}
815
816
817void
818Model::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
831void
832Model::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
851status_t
852Model::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
873bool
874Model::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
922bool
923Model::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
945bool
946Model::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
988Model::CanHandleResult
989Model::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
1022inline bool
1023IsSuperHandlerSignature(const char* signature)
1024{
1025	return strcasecmp(signature, B_FILE_MIMETYPE) == 0;
1026}
1027
1028
1029enum {
1030	kDontMatch = 0,
1031	kMatchSupertype,
1032	kMatch
1033};
1034
1035
1036static int32
1037MatchMimeTypeString(/*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
1070int32
1071Model::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
1133bool
1134Model::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
1151bool
1152Model::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
1179void
1180Model::GetEntry(BEntry* entry) const
1181{
1182	entry->SetTo(EntryRef());
1183}
1184
1185
1186void
1187Model::GetPath(BPath* path) const
1188{
1189	BEntry entry(EntryRef());
1190	entry.GetPath(path);
1191}
1192
1193
1194bool
1195Model::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
1210ssize_t
1211Model::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
1223ssize_t
1224Model::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
1239status_t
1240Model::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
1261status_t
1262Model::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
1287void
1288Model::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
1415void
1416Model::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
1505namespace BPrivate {
1506
1507#include <stdio.h>
1508
1509void
1510DumpOpenModels(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
1541void
1542InitOpenModelDumping()
1543{
1544	readOnlyOpenModelList = 0;
1545	writableOpenModelList = 0;
1546}
1547
1548}	// namespace BPrivate
1549
1550#endif	// CHECK_OPEN_MODEL_LEAKS
1551