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