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
22IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH 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
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their
32respective holders. All rights reserved.
33*/
34
35// Tracker file system calls.
36
37// APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup -- in
38// other words, you will find a lot of ugly cruft in here
39
40// ToDo:
41// Move most of preflight error checks to the Model level and only keep those
42// that have to do with size, reading/writing and name collisions.
43// Get rid of all the BList based APIs, use BObjectLists.
44// Clean up the error handling, push most of the user interaction out of the
45// low level FS calls.
46
47
48#include <ctype.h>
49#include <errno.h>
50#include <strings.h>
51#include <unistd.h>
52
53#include <Alert.h>
54#include <Application.h>
55#include <Catalog.h>
56#include <Debug.h>
57#include <Directory.h>
58#include <Entry.h>
59#include <FindDirectory.h>
60#include <Locale.h>
61#include <NodeInfo.h>
62#include <Path.h>
63#include <Roster.h>
64#include <Screen.h>
65#include <String.h>
66#include <StringFormat.h>
67#include <SymLink.h>
68#include <Volume.h>
69#include <VolumeRoster.h>
70
71#include <fs_attr.h>
72#include <fs_info.h>
73#include <sys/utsname.h>
74
75#include <AutoLocker.h>
76#include <libroot/libroot_private.h>
77#include <system/syscalls.h>
78#include <system/syscall_load_image.h>
79
80#include "Attributes.h"
81#include "Bitmaps.h"
82#include "Commands.h"
83#include "FSUndoRedo.h"
84#include "FSUtils.h"
85#include "InfoWindow.h"
86#include "MimeTypes.h"
87#include "OverrideAlert.h"
88#include "StatusWindow.h"
89#include "Thread.h"
90#include "Tracker.h"
91#include "TrackerSettings.h"
92#include "Utilities.h"
93#include "VirtualDirectoryManager.h"
94
95
96enum {
97	kUserCanceled = B_ERRORS_END + 1,
98	kCopyCanceled = kUserCanceled,
99	kTrashCanceled
100};
101
102enum ConflictCheckResult {
103	kCanceled = kUserCanceled,
104	kPrompt,
105	kSkipAll,
106	kReplace,
107	kReplaceAll,
108	kNoConflicts
109};
110
111
112namespace BPrivate {
113
114#undef B_TRANSLATION_CONTEXT
115#define B_TRANSLATION_CONTEXT "FSUtils"
116
117static status_t FSDeleteFolder(BEntry*, CopyLoopControl*, bool updateStatus,
118	bool deleteTopDir = true, bool upateFileNameInStatus = false);
119static status_t MoveEntryToTrash(BEntry*, BPoint*, Undo &undo);
120static void LowLevelCopy(BEntry*, StatStruct*, BDirectory*, char* destName,
121	CopyLoopControl*, BPoint*);
122status_t DuplicateTask(BObjectList<entry_ref>* srcList);
123static status_t MoveTask(BObjectList<entry_ref>*, BEntry*, BList*, uint32);
124static status_t _DeleteTask(BObjectList<entry_ref>*, bool);
125static status_t _RestoreTask(BObjectList<entry_ref>*);
126status_t CalcItemsAndSize(CopyLoopControl* loopControl,
127	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
128	off_t* totalSize);
129status_t MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc,
130	uint32 moveMode, const char* newName, Undo &undo,
131	CopyLoopControl* loopControl);
132ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref>* srcList,
133	const BDirectory* destDir, int32* collisionCount, uint32 moveMode);
134status_t CheckName(uint32 moveMode, const BEntry* srcEntry,
135	const BDirectory* destDir, bool multipleCollisions,
136	ConflictCheckResult &);
137void CopyAttributes(CopyLoopControl* control, BNode* srcNode,
138	BNode* destNode, void* buffer, size_t bufsize);
139void CopyPoseLocation(BNode* src, BNode* dest);
140bool DirectoryMatchesOrContains(const BEntry*, directory_which);
141bool DirectoryMatchesOrContains(const BEntry*, const char* additionalPath,
142	directory_which);
143bool DirectoryMatches(const BEntry*, directory_which);
144bool DirectoryMatches(const BEntry*, const char* additionalPath,
145	directory_which);
146
147status_t empty_trash(void*);
148
149
150static const char* kDeleteConfirmationStr =
151	B_TRANSLATE_MARK("Are you sure you want to delete the "
152	"selected item(s)? This operation cannot be reverted.");
153
154static const char* kReplaceStr =
155	B_TRANSLATE_MARK("You are trying to replace the item:\n"
156	"\t%name%dest\n"
157	"with:\n"
158	"\t%name%src\n\n"
159	"Would you like to replace it with the one you are %movemode?");
160
161static const char* kDirectoryReplaceStr =
162	B_TRANSLATE_MARK("An item named \"%name\" already exists in "
163	"this folder, and may contain\nitems with the same names. Would you like "
164	"to replace them with those contained in the folder you are %verb?");
165
166static const char* kSymLinkReplaceStr =
167	B_TRANSLATE_MARK("An item named \"%name\" already exists in this "
168	"folder. Would you like to replace it with the symbolic link you are "
169	"creating?");
170
171static const char* kNoFreeSpace =
172	B_TRANSLATE_MARK("Sorry, there is not enough free space on the "
173	"destination volume to copy the selection.");
174
175static const char* kFileErrorString =
176	B_TRANSLATE_MARK("Error copying file \"%name\":\n\t%error\n\n"
177	"Would you like to continue?");
178
179static const char* kFolderErrorString =
180	B_TRANSLATE_MARK("Error copying folder \"%name\":\n\t%error\n\n"
181	"Would you like to continue?");
182
183static const char* kFileDeleteErrorString =
184	B_TRANSLATE_MARK("There was an error deleting \"%name\""
185	":\n\t%error");
186
187static const char* kReplaceManyStr =
188	B_TRANSLATE_MARK("Some items already exist in this folder with "
189	"the same names as the items you are %verb.\n \nWould you like to "
190	"replace them with the ones you are %verb or be prompted for each "
191	"one?");
192
193static const char* kFindAlternativeStr =
194	B_TRANSLATE_MARK("Would you like to find some other suitable "
195	"application?");
196
197static const char* kFindApplicationStr =
198	B_TRANSLATE_MARK("Would you like to find a suitable application "
199	"to open the file?");
200
201
202// Skip these attributes when copying in Tracker
203const char* kSkipAttributes[] = {
204	kAttrPoseInfo,
205	NULL
206};
207
208
209// #pragma mark - CopyLoopControl
210
211
212CopyLoopControl::~CopyLoopControl()
213{
214}
215
216
217void
218CopyLoopControl::Init(uint32 jobKind)
219{
220}
221
222
223void
224CopyLoopControl::Init(int32 totalItems, off_t totalSize,
225	const entry_ref* destDir, bool showCount)
226{
227}
228
229
230bool
231CopyLoopControl::FileError(const char* message, const char* name,
232	status_t error, bool allowContinue)
233{
234	return false;
235}
236
237
238void
239CopyLoopControl::UpdateStatus(const char* name, const entry_ref& ref,
240	int32 count, bool optional)
241{
242}
243
244
245bool
246CopyLoopControl::CheckUserCanceled()
247{
248	return false;
249}
250
251
252CopyLoopControl::OverwriteMode
253CopyLoopControl::OverwriteOnConflict(const BEntry* srcEntry,
254	const char* destName, const BDirectory* destDir, bool srcIsDir,
255	bool dstIsDir)
256{
257	return kReplace;
258}
259
260
261bool
262CopyLoopControl::SkipEntry(const BEntry*, bool)
263{
264	// Tracker makes no exceptions
265	return false;
266}
267
268
269void
270CopyLoopControl::ChecksumChunk(const char*, size_t)
271{
272}
273
274
275bool
276CopyLoopControl::ChecksumFile(const entry_ref*)
277{
278	return true;
279}
280
281
282bool
283CopyLoopControl::SkipAttribute(const char*)
284{
285	return false;
286}
287
288
289bool
290CopyLoopControl::PreserveAttribute(const char*)
291{
292	return false;
293}
294
295
296// #pragma mark - TrackerCopyLoopControl
297
298
299TrackerCopyLoopControl::TrackerCopyLoopControl()
300	:
301	fThread(find_thread(NULL)),
302	fSourceList(NULL)
303{
304}
305
306
307TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind)
308	:
309	fThread(find_thread(NULL)),
310	fSourceList(NULL)
311{
312	Init(jobKind);
313}
314
315
316TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems,
317		off_t totalSize)
318	:
319	fThread(find_thread(NULL)),
320	fSourceList(NULL)
321{
322	Init(totalItems, totalSize);
323}
324
325
326TrackerCopyLoopControl::~TrackerCopyLoopControl()
327{
328	if (gStatusWindow != NULL)
329		gStatusWindow->RemoveStatusItem(fThread);
330}
331
332
333void
334TrackerCopyLoopControl::Init(uint32 jobKind)
335{
336	if (gStatusWindow != NULL)
337		gStatusWindow->CreateStatusItem(fThread, (StatusWindowState)jobKind);
338}
339
340
341void
342TrackerCopyLoopControl::Init(int32 totalItems, off_t totalSize,
343	const entry_ref* destDir, bool showCount)
344{
345	if (gStatusWindow != NULL) {
346		gStatusWindow->InitStatusItem(fThread, totalItems, totalSize,
347			destDir, showCount);
348	}
349}
350
351
352bool
353TrackerCopyLoopControl::FileError(const char* message, const char* name,
354	status_t error, bool allowContinue)
355{
356	BString buffer(message);
357	buffer.ReplaceFirst("%name", name);
358	buffer.ReplaceFirst("%error", strerror(error));
359
360	if (allowContinue) {
361		BAlert* alert = new BAlert("", buffer.String(),	B_TRANSLATE("Cancel"),
362			B_TRANSLATE("OK"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
363		alert->SetShortcut(0, B_ESCAPE);
364		return alert->Go() != 0;
365	}
366
367	BAlert* alert = new BAlert("", buffer.String(),	B_TRANSLATE("Cancel"), 0, 0,
368		B_WIDTH_AS_USUAL, B_STOP_ALERT);
369	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
370	alert->Go();
371	return false;
372}
373
374
375void
376TrackerCopyLoopControl::UpdateStatus(const char* name, const entry_ref&,
377	int32 count, bool optional)
378{
379	if (gStatusWindow != NULL)
380		gStatusWindow->UpdateStatus(fThread, name, count, optional);
381}
382
383
384bool
385TrackerCopyLoopControl::CheckUserCanceled()
386{
387	if (gStatusWindow == NULL)
388		return false;
389
390	if (gStatusWindow->CheckCanceledOrPaused(fThread))
391		return true;
392
393	if (fSourceList != NULL) {
394		// TODO: Check if the user dropped additional files onto this job.
395//		printf("%p->CheckUserCanceled()\n", this);
396	}
397
398	return false;
399}
400
401
402bool
403TrackerCopyLoopControl::SkipAttribute(const char* attributeName)
404{
405	for (const char** skipAttribute = kSkipAttributes; *skipAttribute;
406		skipAttribute++) {
407		if (strcmp(*skipAttribute, attributeName) == 0)
408			return true;
409	}
410
411	return false;
412}
413
414
415void
416TrackerCopyLoopControl::SetSourceList(EntryList* list)
417{
418	fSourceList = list;
419}
420
421
422// #pragma mark - the rest
423
424
425static BNode*
426GetWritableNode(BEntry* entry, StatStruct* statBuf = 0)
427{
428	// utility call that works around the problem with BNodes not being
429	// universally writeable
430	// BNodes created on files will fail to WriteAttr because they do not
431	// have the right r/w permissions
432
433	StatStruct localStatbuf;
434
435	if (!statBuf) {
436		statBuf = &localStatbuf;
437		if (entry->GetStat(statBuf) != B_OK)
438			return 0;
439	}
440
441	if (S_ISREG(statBuf->st_mode))
442		return new BFile(entry, O_RDWR);
443
444	return new BNode(entry);
445}
446
447
448bool
449CheckDevicesEqual(const entry_ref* srcRef, const Model* targetModel)
450{
451	BDirectory destDir (targetModel->EntryRef());
452	struct stat deststat;
453	destDir.GetStat(&deststat);
454
455	return srcRef->device == deststat.st_dev;
456}
457
458
459status_t
460FSSetPoseLocation(ino_t destDirInode, BNode* destNode, BPoint point)
461{
462	PoseInfo poseInfo;
463	poseInfo.fInvisible = false;
464	poseInfo.fInitedDirectory = destDirInode;
465	poseInfo.fLocation = point;
466
467	status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
468		&poseInfo, sizeof(poseInfo));
469
470	if (result == sizeof(poseInfo))
471		return B_OK;
472
473	return result;
474}
475
476
477status_t
478FSSetPoseLocation(BEntry* entry, BPoint point)
479{
480	BNode node(entry);
481	status_t result = node.InitCheck();
482	if (result != B_OK)
483		return result;
484
485	BDirectory parent;
486	result = entry->GetParent(&parent);
487	if (result != B_OK)
488		return result;
489
490	node_ref destNodeRef;
491	result = parent.GetNodeRef(&destNodeRef);
492	if (result != B_OK)
493		return result;
494
495	return FSSetPoseLocation(destNodeRef.node, &node, point);
496}
497
498
499bool
500FSGetPoseLocation(const BNode* node, BPoint* point)
501{
502	PoseInfo poseInfo;
503	if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign,
504		B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap)
505			== kReadAttrFailed) {
506		return false;
507	}
508
509	if (poseInfo.fInitedDirectory == -1LL)
510		return false;
511
512	*point = poseInfo.fLocation;
513
514	return true;
515}
516
517
518static void
519SetUpPoseLocation(ino_t sourceParentIno, ino_t destParentIno,
520	const BNode* sourceNode, BNode* destNode, BPoint* loc)
521{
522	BPoint point;
523	if (loc == NULL
524		// we don't have a position yet
525		&& sourceParentIno != destParentIno
526		// we aren't  copying into the same directory
527		&& FSGetPoseLocation(sourceNode, &point)) {
528		// the original has a valid inited location
529		loc = &point;
530		// copy the originals location
531	}
532
533	if (loc != NULL && loc != (BPoint*)-1) {
534		// loc of -1 is used when copying/moving into a window in list mode
535		// where copying positions would not work
536		// ToSo:
537		// should push all this logic to upper levels
538		FSSetPoseLocation(destParentIno, destNode, *loc);
539	}
540}
541
542
543void
544FSMoveToFolder(BObjectList<entry_ref>* srcList, BEntry* destEntry,
545	uint32 moveMode, BList* pointList)
546{
547	if (srcList->IsEmpty()) {
548		delete srcList;
549		delete pointList;
550		delete destEntry;
551		return;
552	}
553
554	LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
555		destEntry, pointList, moveMode);
556}
557
558
559void
560FSDelete(entry_ref* ref, bool async, bool confirm)
561{
562	BObjectList<entry_ref>* list = new BObjectList<entry_ref>(1, true);
563	list->AddItem(ref);
564	FSDeleteRefList(list, async, confirm);
565}
566
567
568void
569FSDeleteRefList(BObjectList<entry_ref>* list, bool async, bool confirm)
570{
571	if (async) {
572		LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list,
573			confirm);
574	} else
575		_DeleteTask(list, confirm);
576}
577
578
579void
580FSRestoreRefList(BObjectList<entry_ref>* list, bool async)
581{
582	if (async) {
583		LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask,
584			list);
585	} else
586		_RestoreTask(list);
587}
588
589
590void
591FSMoveToTrash(BObjectList<entry_ref>* srcList, BList* pointList, bool async)
592{
593	if (srcList->IsEmpty()) {
594		delete srcList;
595		delete pointList;
596		return;
597	}
598
599	if (async)
600		LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
601			(BEntry*)0, pointList, kMoveSelectionTo);
602	else
603		MoveTask(srcList, 0, pointList, kMoveSelectionTo);
604}
605
606
607static bool
608IsDisksWindowIcon(BEntry* entry)
609{
610	BPath path;
611	if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
612		return false;
613
614	return strcmp(path.Path(), "/") == 0;
615}
616
617enum {
618	kNotConfirmed,
619	kConfirmedHomeMove,
620	kConfirmedAll
621};
622
623
624bool
625ConfirmChangeIfWellKnownDirectory(const BEntry* entry, DestructiveAction action,
626	bool dontAsk, int32* confirmedAlready)
627{
628	// Don't let the user casually move/change important files/folders
629	//
630	// This is a cheap replacement for having a real UID support turned
631	// on and not running as root all the time
632
633	if (confirmedAlready && *confirmedAlready == kConfirmedAll)
634		return true;
635
636	if (FSIsDeskDir(entry) || FSIsTrashDir(entry) || FSIsRootDir(entry))
637		return false;
638
639	if ((!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)
640		&& !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY))
641		|| DirectoryMatchesOrContains(entry, B_SYSTEM_TEMP_DIRECTORY))
642		// quick way out
643		return true;
644
645	BString warning;
646	bool requireOverride = true;
647
648	if (DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)) {
649		if (action == kRename) {
650			warning.SetTo(
651				B_TRANSLATE("If you rename the system folder or its "
652				"contents, you won't be able to boot %osName!\n\nAre you sure "
653				"you want to do this?\n\nTo rename the system folder or its "
654				"contents anyway, hold down the Shift key and click "
655				"\"Rename\"."));
656		} else if(action == kMove) {
657			warning.SetTo(
658				B_TRANSLATE("If you move the system folder or its "
659				"contents, you won't be able to boot %osName!\n\nAre you sure "
660				"you want to do this?\n\nTo move the system folder or its "
661				"contents anyway, hold down the Shift key and click "
662				"\"Move\"."));
663		} else {
664			warning.SetTo(
665				B_TRANSLATE("If you alter the system folder or its "
666				"contents, you won't be able to boot %osName!\n\nAre you sure "
667				"you want to do this?\n\nTo alter the system folder or its "
668				"contents anyway, hold down the Shift key and click "
669				"\"I know what I'm doing\"."));
670		}
671	} else if (DirectoryMatches(entry, B_USER_DIRECTORY)) {
672		if (action == kRename) {
673			warning .SetTo(
674				B_TRANSLATE("If you rename the home folder, %osName "
675				"may not behave properly!\n\nAre you sure you want to do this?"
676				"\n\nTo rename the home folder anyway, hold down the "
677				"Shift key and click \"Rename\"."));
678		} else if (action == kMove) {
679			warning .SetTo(
680				B_TRANSLATE("If you move the home folder, %osName "
681				"may not behave properly!\n\nAre you sure you want to do this?"
682				"\n\nTo move the home folder anyway, hold down the "
683				"Shift key and click \"Move\"."));
684		} else {
685			warning .SetTo(
686				B_TRANSLATE("If you alter the home folder, %osName "
687				"may not behave properly!\n\nAre you sure you want to do this?"
688				"\n\nTo alter the home folder anyway, hold down the "
689				"Shift key and click \"I know what I'm doing\"."));
690		}
691	} else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY)
692		|| DirectoryMatchesOrContains(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
693		if (action == kRename) {
694			warning.SetTo(
695				B_TRANSLATE("If you rename %target, %osName may not behave "
696				"properly!\n\nAre you sure you want to do this?"));
697		} else if (action == kMove) {
698			warning.SetTo(
699				B_TRANSLATE("If you move %target, %osName may not behave "
700				"properly!\n\nAre you sure you want to do this?"));
701		} else {
702			warning.SetTo(
703				B_TRANSLATE("If you alter %target, %osName may not behave "
704				"properly!\n\nAre you sure you want to do this?"));
705		}
706
707		if (DirectoryMatchesOrContains(entry, "beos_mime",
708				B_USER_SETTINGS_DIRECTORY)
709			|| DirectoryMatchesOrContains(entry, "beos_mime",
710				B_SYSTEM_SETTINGS_DIRECTORY)) {
711			warning.ReplaceFirst("%target", B_TRANSLATE("the MIME settings"));
712			requireOverride = false;
713		} else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) {
714			warning.ReplaceFirst("%target", B_TRANSLATE("the config folder"));
715			requireOverride = false;
716		} else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY)
717			|| DirectoryMatches(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
718			warning.ReplaceFirst("%target", B_TRANSLATE("the settings folder"));
719			requireOverride = false;
720		} else {
721			// It was not a special directory/file after all. Allow renaming.
722			return true;
723		}
724	} else
725		return true;
726
727	if (dontAsk)
728		return false;
729
730	if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove
731		&& !requireOverride)
732		// we already warned about moving home this time around
733		return true;
734
735	struct utsname name;
736	if (uname(&name) == -1)
737		warning.ReplaceFirst("%osName", "Haiku");
738	else
739		warning.ReplaceFirst("%osName", name.sysname);
740
741	BString buttonLabel;
742	if (action == kRename) {
743		buttonLabel = B_TRANSLATE_COMMENT("Rename", "button label");
744	} else if (action == kMove) {
745		buttonLabel = B_TRANSLATE_COMMENT("Move", "button label");
746	} else {
747		buttonLabel = B_TRANSLATE_COMMENT("I know what I'm doing",
748			"button label");
749	}
750
751	OverrideAlert* alert = new OverrideAlert("", warning.String(),
752		buttonLabel.String(), (requireOverride ? B_SHIFT_KEY : 0),
753		B_TRANSLATE("Cancel"), 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
754	alert->SetShortcut(1, B_ESCAPE);
755	if (alert->Go() == 1) {
756		if (confirmedAlready)
757			*confirmedAlready = kNotConfirmed;
758		return false;
759	}
760
761	if (confirmedAlready) {
762		if (!requireOverride)
763			*confirmedAlready = kConfirmedHomeMove;
764		else
765			*confirmedAlready = kConfirmedAll;
766	}
767
768	return true;
769}
770
771
772static status_t
773InitCopy(CopyLoopControl* loopControl, uint32 moveMode,
774	BObjectList<entry_ref>* srcList, BVolume* dstVol, BDirectory* destDir,
775	entry_ref* destRef, bool preflightNameCheck, bool needSizeCalculation,
776	int32* collisionCount, ConflictCheckResult* preflightResult)
777{
778	if (dstVol->IsReadOnly()) {
779		BAlert* alert = new BAlert("",
780			B_TRANSLATE("You can't move or copy items to read-only volumes."),
781			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
782		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
783		alert->Go();
784		return B_ERROR;
785	}
786
787	int32 numItems = srcList->CountItems();
788	int32 askOnceOnly = kNotConfirmed;
789	for (int32 index = 0; index < numItems; index++) {
790		// we could check for this while iterating through items in each of
791		// the copy loops, except it takes forever to call CalcItemsAndSize
792		BEntry entry((entry_ref*)srcList->ItemAt(index));
793		if (IsDisksWindowIcon(&entry)) {
794			BString errorStr;
795			if (moveMode == kCreateLink) {
796				errorStr.SetTo(
797					B_TRANSLATE("You cannot create a link to the root "
798					"directory."));
799			} else {
800				errorStr.SetTo(
801					B_TRANSLATE("You cannot copy or move the root "
802					"directory."));
803			}
804
805			BAlert* alert = new BAlert("", errorStr.String(),
806				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
807				B_WARNING_ALERT);
808			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
809			alert->Go();
810			return B_ERROR;
811		}
812		if (moveMode == kMoveSelectionTo
813			&& !ConfirmChangeIfWellKnownDirectory(&entry, kMove,
814				false, &askOnceOnly)) {
815			return B_ERROR;
816		}
817	}
818
819	if (preflightNameCheck) {
820		ASSERT(collisionCount);
821		ASSERT(preflightResult);
822
823		*preflightResult = kPrompt;
824		*collisionCount = 0;
825
826		*preflightResult = PreFlightNameCheck(srcList, destDir,
827			collisionCount, moveMode);
828		if (*preflightResult == kCanceled) {
829			// user canceled
830			return B_ERROR;
831		}
832	}
833
834	// set up the status display
835	switch (moveMode) {
836		case kCopySelectionTo:
837		case kDuplicateSelection:
838		case kMoveSelectionTo:
839			{
840				loopControl->Init(moveMode == kMoveSelectionTo ? kMoveState
841					: kCopyState);
842
843				int32 totalItems = 0;
844				off_t totalSize = 0;
845				if (needSizeCalculation) {
846					if (CalcItemsAndSize(loopControl, srcList,
847							dstVol->BlockSize(), &totalItems, &totalSize)
848							!= B_OK) {
849						return B_ERROR;
850					}
851
852					// check for free space before starting copy
853					if ((totalSize + (4* kKBSize)) >= dstVol->FreeBytes()) {
854						BAlert* alert = new BAlert("",
855							B_TRANSLATE_NOCOLLECT(kNoFreeSpace),
856							B_TRANSLATE("Cancel"),
857							0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
858						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
859						alert->Go();
860						return B_ERROR;
861					}
862				}
863
864				loopControl->Init(totalItems, totalSize, destRef);
865				break;
866			}
867
868		case kCreateLink:
869			if (numItems > 10) {
870				// this will be fast, only put up status if lots of items
871				// moved, links created
872				loopControl->Init(kCreateLinkState);
873				loopControl->Init(numItems, numItems, destRef);
874			}
875			break;
876	}
877
878	return B_OK;
879}
880
881
882// ToDo:
883// get rid of this cruft
884bool
885delete_ref(void* ref)
886{
887	delete (entry_ref*)ref;
888	return false;
889}
890
891
892bool
893delete_point(void* point)
894{
895	delete (BPoint*)point;
896	return false;
897}
898
899
900static status_t
901MoveTask(BObjectList<entry_ref>* srcList, BEntry* destEntry, BList* pointList,
902	uint32 moveMode)
903{
904	ASSERT(!srcList->IsEmpty());
905
906	// extract information from src, dest models
907	// ## note that we're assuming all items come from the same volume
908	// ## by looking only at FirstItem here which is not a good idea
909	dev_t srcVolumeDevice = srcList->FirstItem()->device;
910	dev_t destVolumeDevice = srcVolumeDevice;
911
912	StatStruct deststat;
913	BVolume volume(srcVolumeDevice);
914	entry_ref destRef;
915
916	bool destIsTrash = false;
917	BDirectory destDir;
918	BDirectory* destDirToCheck = NULL;
919	bool needPreflightNameCheck = false;
920	bool sourceIsReadOnly = volume.IsReadOnly();
921	volume.Unset();
922
923	bool fromUndo = FSIsUndoMoveMode(moveMode);
924	moveMode = FSMoveMode(moveMode);
925
926	// if we're not passed a destEntry then we are supposed to move to trash
927	if (destEntry != NULL) {
928		destEntry->GetRef(&destRef);
929
930		destDir.SetTo(destEntry);
931		destDir.GetStat(&deststat);
932		destDirToCheck = &destDir;
933
934		destVolumeDevice = deststat.st_dev;
935		destIsTrash = FSIsTrashDir(destEntry);
936		volume.SetTo(destVolumeDevice);
937
938		needPreflightNameCheck = true;
939	} else if (moveMode == kDuplicateSelection) {
940		BEntry entry;
941		entry.SetTo(srcList->FirstItem());
942		entry.GetParent(&destDir);
943		volume.SetTo(srcVolumeDevice);
944	} else {
945		// move is to trash
946		destIsTrash = true;
947
948		FSGetTrashDir(&destDir, srcVolumeDevice);
949		volume.SetTo(srcVolumeDevice);
950
951		BEntry entry;
952		destDir.GetEntry(&entry);
953		destDirToCheck = &destDir;
954
955		entry.GetRef(&destRef);
956	}
957
958	// change the move mode if needed
959	if (moveMode == kCopySelectionTo && destIsTrash) {
960		// cannot copy to trash
961		moveMode = kMoveSelectionTo;
962	}
963
964	if (moveMode == kMoveSelectionTo && sourceIsReadOnly)
965		moveMode = kCopySelectionTo;
966
967	bool needSizeCalculation = true;
968	if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice)
969		|| destIsTrash) {
970		needSizeCalculation = false;
971	}
972
973	// we need the undo object later on, so we create it no matter
974	// if we really need it or not (it's very lightweight)
975	MoveCopyUndo undo(srcList, destDir, pointList, moveMode);
976	if (fromUndo)
977		undo.Remove();
978
979	TrackerCopyLoopControl loopControl;
980
981	ConflictCheckResult conflictCheckResult = kPrompt;
982	int32 collisionCount = 0;
983	// TODO: Status item is created in InitCopy(), but it would be kind of
984	// neat to move all that into TrackerCopyLoopControl
985	status_t result = InitCopy(&loopControl, moveMode, srcList,
986		&volume, destDirToCheck, &destRef, needPreflightNameCheck,
987		needSizeCalculation, &collisionCount, &conflictCheckResult);
988
989	loopControl.SetSourceList(srcList);
990
991	if (result == B_OK) {
992		for (int32 i = 0; i < srcList->CountItems(); i++) {
993			BPoint* loc = (BPoint*)-1;
994				// a loc of -1 forces autoplacement, rather than copying the
995				// position of the original node
996				// TODO:
997				// Clean this mess up!
998				// What could be a cleaner design is to pass along some kind
999				// "filter" object that post-processes poses, i.e. adds the
1000				// location or other stuff. It should not be a job of the
1001				// copy-engine.
1002
1003			entry_ref* srcRef = srcList->ItemAt(i);
1004
1005			if (moveMode == kDuplicateSelection) {
1006				BEntry entry(srcRef);
1007				entry.GetParent(&destDir);
1008				destDir.GetStat(&deststat);
1009				volume.SetTo(srcRef->device);
1010			}
1011
1012			// handle case where item is dropped into folder it already lives
1013			// in which could happen if dragging from a query window
1014			if (moveMode != kCreateLink
1015				&& moveMode != kCreateRelativeLink
1016				&& moveMode != kDuplicateSelection
1017				&& !destIsTrash
1018				&& (srcRef->device == destRef.device
1019				&& srcRef->directory == deststat.st_ino)) {
1020				continue;
1021			}
1022
1023			if (loopControl.CheckUserCanceled())
1024				break;
1025
1026			BEntry sourceEntry(srcRef);
1027			if (sourceEntry.InitCheck() != B_OK) {
1028				BString error(B_TRANSLATE("Error moving \"%name\"."));
1029				error.ReplaceFirst("%name", srcRef->name);
1030				BAlert* alert = new BAlert("", error.String(),
1031					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1032					B_WARNING_ALERT);
1033				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1034				alert->Go();
1035				break;
1036			}
1037
1038			// are we moving item to trash?
1039			if (destIsTrash) {
1040				if (pointList != NULL)
1041					loc = (BPoint*)pointList->ItemAt(i);
1042
1043				result = MoveEntryToTrash(&sourceEntry, loc, undo);
1044				if (result != B_OK) {
1045					BString error(B_TRANSLATE("Error moving \"%name\" to Trash. "
1046						"(%error)"));
1047					error.ReplaceFirst("%name", srcRef->name);
1048					error.ReplaceFirst("%error", strerror(result));
1049					BAlert* alert = new BAlert("", error.String(),
1050						B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1051						B_WARNING_ALERT);
1052					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1053					alert->Go();
1054					break;
1055				}
1056				continue;
1057			}
1058
1059			// resolve name collisions and hierarchy problems
1060			if (CheckName(moveMode, &sourceEntry, &destDir,
1061					collisionCount > 1, conflictCheckResult) != B_OK) {
1062				// we will skip the current item, because we got a conflict
1063				// and were asked to or because there was some conflict
1064
1065				// update the status because item got skipped and the status
1066				// will not get updated by the move call
1067				loopControl.UpdateStatus(srcRef->name, *srcRef, 1);
1068
1069				continue;
1070			}
1071
1072			// get location to place this item
1073			if (pointList && moveMode != kCopySelectionTo) {
1074				loc = (BPoint*)pointList->ItemAt(i);
1075
1076				BNode* src_node = GetWritableNode(&sourceEntry);
1077				if (src_node && src_node->InitCheck() == B_OK) {
1078					PoseInfo poseInfo;
1079					poseInfo.fInvisible = false;
1080					poseInfo.fInitedDirectory = deststat.st_ino;
1081					poseInfo.fLocation = *loc;
1082					src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
1083						&poseInfo, sizeof(poseInfo));
1084				}
1085				delete src_node;
1086			}
1087
1088			if (pointList)
1089 				loc = (BPoint*)pointList->ItemAt(i);
1090
1091			result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL,
1092				undo, &loopControl);
1093			if (result != B_OK)
1094				break;
1095		}
1096	}
1097
1098	// duplicates of srcList, destFolder were created - dispose them
1099	delete srcList;
1100	delete destEntry;
1101
1102	// delete file location list and all Points within
1103	if (pointList != NULL) {
1104		pointList->DoForEach(delete_point);
1105		delete pointList;
1106	}
1107
1108	return B_OK;
1109}
1110
1111
1112class FailWithAlert {
1113	public:
1114		static void FailOnError(status_t error, const char* string,
1115			const char* name = NULL)
1116		{
1117			if (error != B_OK)
1118				throw FailWithAlert(error, string, name);
1119		}
1120
1121		FailWithAlert(status_t error, const char* string, const char* name)
1122			:
1123			fString(string),
1124			fName(name),
1125			fError(error)
1126		{
1127		}
1128
1129		const char* fString;
1130		const char* fName;
1131		status_t fError;
1132};
1133
1134
1135class MoveError {
1136public:
1137	static void FailOnError(status_t error)
1138	{
1139		if (error != B_OK)
1140			throw MoveError(error);
1141	}
1142
1143	MoveError(status_t error)
1144		:
1145		fError(error)
1146	{
1147	}
1148
1149	status_t fError;
1150};
1151
1152
1153void
1154CopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
1155	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1156	Undo &undo)
1157{
1158	if (loopControl->SkipEntry(srcFile, true))
1159		return;
1160
1161	node_ref node;
1162	destDir->GetNodeRef(&node);
1163	BVolume volume(node.device);
1164
1165	// check for free space first
1166	if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) {
1167		loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "",
1168			B_DEVICE_FULL, false);
1169		throw (status_t)B_DEVICE_FULL;
1170	}
1171
1172	char destName[B_FILE_NAME_LENGTH];
1173	srcFile->GetName(destName);
1174	entry_ref ref;
1175	srcFile->GetRef(&ref);
1176
1177	loopControl->UpdateStatus(destName, ref, 1024, true);
1178
1179	if (makeOriginalName) {
1180		BString suffix(" ");
1181		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1182		FSMakeOriginalName(destName, destDir, suffix.String());
1183		undo.UpdateEntry(srcFile, destName);
1184	}
1185
1186	BEntry conflictingEntry;
1187	if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) {
1188		switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir,
1189				false, false)) {
1190			case TrackerCopyLoopControl::kSkip:
1191				// we are about to ignore this entire directory
1192				return;
1193
1194			case TrackerCopyLoopControl::kReplace:
1195				if (!conflictingEntry.IsDirectory()) {
1196					ThrowOnError(conflictingEntry.Remove());
1197					break;
1198				}
1199				// fall through if not a directory
1200			case TrackerCopyLoopControl::kMerge:
1201				// This flag implies that the attributes should be kept
1202				// on the file.  Just ignore it.
1203				break;
1204		}
1205	}
1206
1207	try {
1208		LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc);
1209	} catch (status_t err) {
1210		if (err == kCopyCanceled)
1211			throw (status_t)err;
1212
1213		if (err != B_OK) {
1214			if (!loopControl->FileError(
1215					B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err,
1216					true)) {
1217				throw (status_t)err;
1218			} else {
1219				// user selected continue in spite of error, update status bar
1220				loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size);
1221			}
1222		}
1223	}
1224}
1225
1226
1227#ifdef _SILENTLY_CORRECT_FILE_NAMES
1228static bool
1229CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName)
1230{
1231	// Is it a FAT32 file system?
1232	// (this is the only one we currently know about)
1233
1234	BEntry target;
1235	destDir->GetEntry(&target);
1236	entry_ref targetRef;
1237	fs_info info;
1238	if (target.GetRef(&targetRef) == B_OK
1239		&& fs_stat_dev(targetRef.device, &info) == B_OK
1240		&& !strcmp(info.fsh_name, "fat")) {
1241		bool wasInvalid = false;
1242
1243		// it's a FAT32 file system, now check the name
1244
1245		int32 length = strlen(destName) - 1;
1246		while (destName[length] == '.') {
1247			// invalid name, just cut off the dot at the end
1248			destName[length--] = '\0';
1249			wasInvalid = true;
1250		}
1251
1252		char* invalid = destName;
1253		while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) {
1254			invalid[0] = '_';
1255			wasInvalid = true;
1256		}
1257
1258		return wasInvalid;
1259	}
1260
1261	return false;
1262}
1263#endif
1264
1265
1266static void
1267LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir,
1268	char* destName, CopyLoopControl* loopControl, BPoint* loc)
1269{
1270	entry_ref ref;
1271	ThrowOnError(srcEntry->GetRef(&ref));
1272
1273	if (S_ISLNK(srcStat->st_mode)) {
1274		// handle symbolic links
1275		BSymLink srcLink;
1276		BSymLink newLink;
1277		char linkpath[MAXPATHLEN];
1278
1279		ThrowOnError(srcLink.SetTo(srcEntry));
1280		ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1);
1281		if (size < 0)
1282			ThrowOnError(size);
1283		ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink));
1284
1285		node_ref destNodeRef;
1286		destDir->GetNodeRef(&destNodeRef);
1287		// copy or write new pose location as a first thing
1288		SetUpPoseLocation(ref.directory, destNodeRef.node, &srcLink,
1289			&newLink, loc);
1290
1291		BNodeInfo nodeInfo(&newLink);
1292		nodeInfo.SetType(B_LINK_MIMETYPE);
1293
1294		newLink.SetPermissions(srcStat->st_mode);
1295		newLink.SetOwner(srcStat->st_uid);
1296		newLink.SetGroup(srcStat->st_gid);
1297		newLink.SetModificationTime(srcStat->st_mtime);
1298		newLink.SetCreationTime(srcStat->st_crtime);
1299
1300		return;
1301	}
1302
1303	BFile srcFile(srcEntry, O_RDONLY);
1304	ThrowOnInitCheckError(&srcFile);
1305
1306	const size_t kMinBufferSize = 1024* 128;
1307	const size_t kMaxBufferSize = 1024* 1024;
1308
1309	size_t bufsize = kMinBufferSize;
1310	if ((off_t)bufsize < srcStat->st_size) {
1311		//	File bigger than the buffer size: determine an optimal buffer size
1312		system_info sinfo;
1313		get_system_info(&sinfo);
1314		size_t freesize = static_cast<size_t>(
1315			(sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE);
1316		bufsize = freesize / 4;
1317			// take 1/4 of RAM max
1318		bufsize -= bufsize % (16* 1024);
1319			// Round to 16 KB boundaries
1320		if (bufsize < kMinBufferSize) {
1321			// at least kMinBufferSize
1322			bufsize = kMinBufferSize;
1323		} else if (bufsize > kMaxBufferSize) {
1324			// no more than kMaxBufferSize
1325			bufsize = kMaxBufferSize;
1326		}
1327	}
1328
1329	BFile destFile(destDir, destName, O_RDWR | O_CREAT);
1330#ifdef _SILENTLY_CORRECT_FILE_NAMES
1331	if ((destFile.InitCheck() == B_BAD_VALUE
1332		|| destFile.InitCheck() == B_NOT_ALLOWED)
1333		&& CreateFileSystemCompatibleName(destDir, destName)) {
1334		destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE);
1335	}
1336#endif
1337
1338	ThrowOnInitCheckError(&destFile);
1339
1340	node_ref destNodeRef;
1341	destDir->GetNodeRef(&destNodeRef);
1342	// copy or write new pose location as a first thing
1343	SetUpPoseLocation(ref.directory, destNodeRef.node, &srcFile,
1344		&destFile, loc);
1345
1346	char* buffer = new char[bufsize];
1347	try {
1348		// copy data portion of file
1349		while (true) {
1350			if (loopControl->CheckUserCanceled()) {
1351				// if copy was canceled, remove partial destination file
1352				destFile.Unset();
1353
1354				BEntry destEntry;
1355				if (destDir->FindEntry(destName, &destEntry) == B_OK)
1356					destEntry.Remove();
1357
1358				throw (status_t)kCopyCanceled;
1359			}
1360
1361			ASSERT(buffer);
1362			ssize_t bytes = srcFile.Read(buffer, bufsize);
1363
1364			if (bytes > 0) {
1365				ssize_t updateBytes = 0;
1366				if (bytes > 32* 1024) {
1367					// when copying large chunks, update after read and after
1368					// write to get better update granularity
1369					updateBytes = bytes / 2;
1370					loopControl->UpdateStatus(NULL, ref, updateBytes, true);
1371				}
1372
1373				loopControl->ChecksumChunk(buffer, (size_t)bytes);
1374
1375				ssize_t result = destFile.Write(buffer, (size_t)bytes);
1376				if (result != bytes)
1377					throw (status_t)B_ERROR;
1378
1379				loopControl->UpdateStatus(NULL, ref, bytes - updateBytes,
1380					true);
1381			} else if (bytes < 0) {
1382				// read error
1383				throw (status_t)bytes;
1384			} else {
1385				// we are done
1386				break;
1387			}
1388		}
1389
1390		CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize);
1391	} catch (...) {
1392		delete[] buffer;
1393		throw;
1394	}
1395
1396	destFile.SetPermissions(srcStat->st_mode);
1397	destFile.SetOwner(srcStat->st_uid);
1398	destFile.SetGroup(srcStat->st_gid);
1399	destFile.SetModificationTime(srcStat->st_mtime);
1400	destFile.SetCreationTime(srcStat->st_crtime);
1401
1402	delete[] buffer;
1403
1404	if (!loopControl->ChecksumFile(&ref)) {
1405		// File no good.  Remove and quit.
1406		destFile.Unset();
1407
1408		BEntry destEntry;
1409		if (destDir->FindEntry(destName, &destEntry) == B_OK)
1410			destEntry.Remove();
1411		throw (status_t)kUserCanceled;
1412	}
1413}
1414
1415
1416void
1417CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode,
1418	void* buffer, size_t bufsize)
1419{
1420	// ToDo:
1421	// Add error checking
1422	// prior to coyping attributes, make sure indices are installed
1423
1424	// When calling CopyAttributes on files, have to make sure destNode
1425	// is a BFile opened R/W
1426
1427	srcNode->RewindAttrs();
1428	char name[256];
1429	while (srcNode->GetNextAttrName(name) == B_OK) {
1430		// Check to see if this attribute should be skipped.
1431		if (control->SkipAttribute(name))
1432			continue;
1433
1434		attr_info info;
1435		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1436			continue;
1437
1438		// Check to see if this attribute should be overwritten when it
1439		// already exists.
1440		if (control->PreserveAttribute(name)) {
1441			attr_info dest_info;
1442			if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1443				continue;
1444		}
1445
1446		// Special case for a size 0 attribute. It wouldn't be written at all
1447		// otherwise.
1448		if (info.size == 0)
1449			destNode->WriteAttr(name, info.type, 0, buffer, 0);
1450
1451		ssize_t bytes;
1452		ssize_t numToRead = (ssize_t)info.size;
1453		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1454			size_t chunkSize = (size_t)numToRead;
1455			if (chunkSize > bufsize)
1456				chunkSize = bufsize;
1457
1458			bytes = srcNode->ReadAttr(name, info.type, offset,
1459				buffer, chunkSize);
1460
1461			if (bytes <= 0)
1462				break;
1463
1464			destNode->WriteAttr(name, info.type, offset, buffer,
1465				(size_t)bytes);
1466
1467			numToRead -= bytes;
1468		}
1469	}
1470}
1471
1472
1473static void
1474CopyFolder(BEntry* srcEntry, BDirectory* destDir,
1475	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1476	Undo &undo, bool removeSource = false)
1477{
1478	BDirectory newDir;
1479	BEntry entry;
1480	status_t err = B_OK;
1481	bool createDirectory = true;
1482	BEntry existingEntry;
1483
1484	if (loopControl->SkipEntry(srcEntry, false))
1485		return;
1486
1487	entry_ref ref;
1488	srcEntry->GetRef(&ref);
1489
1490	char destName[B_FILE_NAME_LENGTH];
1491	strlcpy(destName, ref.name, sizeof(destName));
1492
1493	loopControl->UpdateStatus(ref.name, ref, 1024, true);
1494
1495	if (makeOriginalName) {
1496		BString suffix(" ");
1497		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1498		FSMakeOriginalName(destName, destDir, suffix.String());
1499		undo.UpdateEntry(srcEntry, destName);
1500	}
1501
1502	if (destDir->FindEntry(destName, &existingEntry) == B_OK) {
1503		// some entry with a conflicting name is already present in destDir
1504		// decide what to do about it
1505		bool isDirectory = existingEntry.IsDirectory();
1506
1507		switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir,
1508			true, isDirectory)) {
1509			case TrackerCopyLoopControl::kSkip:
1510				// we are about to ignore this entire directory
1511				return;
1512
1513
1514			case TrackerCopyLoopControl::kReplace:
1515				if (!isDirectory) {
1516					// conflicting with a file or symbolic link, remove entry
1517					ThrowOnError(existingEntry.Remove());
1518					break;
1519				}
1520			// fall through if directory, do not replace.
1521			case TrackerCopyLoopControl::kMerge:
1522				ASSERT(isDirectory);
1523				// do not create a new directory, use the current one
1524				newDir.SetTo(&existingEntry);
1525				createDirectory = false;
1526				break;
1527		}
1528	}
1529
1530	// loop through everything in src folder and copy it to new folder
1531	BDirectory srcDir(srcEntry);
1532	srcDir.Rewind();
1533
1534	// create a new folder inside of destination folder
1535	if (createDirectory) {
1536	 	err = destDir->CreateDirectory(destName, &newDir);
1537#ifdef _SILENTLY_CORRECT_FILE_NAMES
1538	 	if (err == B_BAD_VALUE) {
1539	 		// check if it's an invalid name on a FAT32 file system
1540	 		if (CreateFileSystemCompatibleName(destDir, destName))
1541	 			err = destDir->CreateDirectory(destName, &newDir);
1542	 	}
1543#endif
1544		if (err != B_OK) {
1545			if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT(
1546					kFolderErrorString), destName, err, true)) {
1547				throw err;
1548			}
1549
1550			// will allow rest of copy to continue
1551			return;
1552		}
1553	}
1554
1555	char* buffer;
1556	if (createDirectory && err == B_OK
1557		&& (buffer = (char*)malloc(32768)) != 0) {
1558		CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768);
1559			// don't copy original pose location if new location passed
1560		free(buffer);
1561	}
1562
1563	StatStruct statbuf;
1564	srcDir.GetStat(&statbuf);
1565	dev_t sourceDeviceID = statbuf.st_dev;
1566
1567	// copy or write new pose location
1568	node_ref destNodeRef;
1569	destDir->GetNodeRef(&destNodeRef);
1570	SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir,
1571		&newDir, loc);
1572
1573	while (srcDir.GetNextEntry(&entry) == B_OK) {
1574
1575		if (loopControl->CheckUserCanceled())
1576			throw (status_t)kUserCanceled;
1577
1578		entry.GetStat(&statbuf);
1579
1580		if (S_ISDIR(statbuf.st_mode)) {
1581
1582			// entry is a mount point, do not copy it
1583			if (statbuf.st_dev != sourceDeviceID) {
1584				PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n",
1585					statbuf.st_dev, sourceDeviceID));
1586				continue;
1587			}
1588
1589			CopyFolder(&entry, &newDir, loopControl, 0, false, undo,
1590				removeSource);
1591			if (removeSource)
1592				FSDeleteFolder(&entry, loopControl, true, true, false);
1593		} else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
1594			CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo);
1595			if (removeSource)
1596				entry.Remove();
1597		} else {
1598			// Ignore special files
1599		}
1600	}
1601	if (removeSource)
1602		srcEntry->Remove();
1603	else
1604		srcEntry->Unset();
1605}
1606
1607
1608status_t
1609RecursiveMove(BEntry* entry, BDirectory* destDir,
1610	CopyLoopControl* loopControl)
1611{
1612	const char* name = entry->Name();
1613
1614	if (destDir->Contains(name)) {
1615		BPath path (destDir, name);
1616		BDirectory subDir (path.Path());
1617		entry_ref ref;
1618		entry->GetRef(&ref);
1619		BDirectory source(&ref);
1620		if (source.InitCheck() == B_OK) {
1621			source.Rewind();
1622			BEntry current;
1623			while (source.GetNextEntry(&current) == B_OK) {
1624				if (current.IsDirectory()) {
1625					RecursiveMove(&current, &subDir, loopControl);
1626					current.Remove();
1627				} else {
1628					name = current.Name();
1629					if (loopControl->OverwriteOnConflict(&current, name,
1630							&subDir, true, false)
1631								!= TrackerCopyLoopControl::kSkip) {
1632						MoveError::FailOnError(current.MoveTo(&subDir,
1633							NULL, true));
1634					}
1635				}
1636			}
1637		}
1638		entry->Remove();
1639	} else
1640		MoveError::FailOnError(entry->MoveTo(destDir));
1641
1642	return B_OK;
1643}
1644
1645status_t
1646MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode,
1647	const char* newName, Undo &undo, CopyLoopControl* loopControl)
1648{
1649	entry_ref ref;
1650	try {
1651		node_ref destNode;
1652		StatStruct statbuf;
1653		MoveError::FailOnError(entry->GetStat(&statbuf));
1654		MoveError::FailOnError(entry->GetRef(&ref));
1655		MoveError::FailOnError(destDir->GetNodeRef(&destNode));
1656
1657		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
1658			PoseInfo poseInfo;
1659			char name[B_FILE_NAME_LENGTH];
1660			strlcpy(name, ref.name, sizeof(name));
1661
1662			BSymLink link;
1663			BString suffix(" ");
1664			suffix << B_TRANSLATE_COMMENT("link", "filename link"),
1665			FSMakeOriginalName(name, destDir, suffix.String());
1666			undo.UpdateEntry(entry, name);
1667
1668			BPath path;
1669			entry->GetPath(&path);
1670			if (loc && loc != (BPoint*)-1) {
1671				poseInfo.fInvisible = false;
1672				poseInfo.fInitedDirectory = destNode.node;
1673				poseInfo.fLocation = *loc;
1674			}
1675
1676			status_t err = B_ERROR;
1677
1678			if (moveMode == kCreateRelativeLink) {
1679				if (statbuf.st_dev == destNode.device) {
1680					// relative link only works on the same device
1681					char oldwd[B_PATH_NAME_LENGTH];
1682					getcwd(oldwd, B_PATH_NAME_LENGTH);
1683
1684					BEntry destEntry;
1685					destDir -> GetEntry(&destEntry);
1686					BPath destPath;
1687					destEntry.GetPath(&destPath);
1688
1689					chdir(destPath.Path());
1690						// change working dir to target dir
1691
1692					BString destString(destPath.Path());
1693					destString.Append("/");
1694
1695					BString srcString(path.Path());
1696					srcString.RemoveLast(path.Leaf());
1697
1698					// find index while paths are the same
1699
1700					const char* src = srcString.String();
1701					const char* dest = destString.String();
1702					const char* lastFolderSrc = src;
1703					const char* lastFolderDest = dest;
1704
1705					while (*src && *dest && *src == *dest) {
1706						++src;
1707						if (*dest++ == '/') {
1708							lastFolderSrc = src;
1709							lastFolderDest = dest;
1710						}
1711					}
1712					src = lastFolderSrc;
1713					dest = lastFolderDest;
1714
1715					BString source;
1716					if (*dest == '\0' && *src != '\0') {
1717						// source is deeper in the same tree than the target
1718						source.Append(src);
1719					} else if (*dest != '\0') {
1720						// target is deeper in the same tree than the source
1721						while (*dest) {
1722							if (*dest == '/')
1723								source.Prepend("../");
1724							++dest;
1725						}
1726						source.Append(src);
1727					}
1728
1729					// else source and target are in the same dir
1730
1731					source.Append(path.Leaf());
1732					err = destDir->CreateSymLink(name, source.String(),
1733						&link);
1734
1735					chdir(oldwd);
1736						// change working dir back to original
1737				} else
1738					moveMode = kCreateLink;
1739						// fall back to absolute link mode
1740			}
1741
1742			if (moveMode == kCreateLink)
1743				err = destDir->CreateSymLink(name, path.Path(), &link);
1744
1745			if (err == B_UNSUPPORTED) {
1746				throw FailWithAlert(err,
1747					B_TRANSLATE("The target disk does not support "
1748					"creating links."), NULL);
1749			}
1750
1751			FailWithAlert::FailOnError(err,
1752				B_TRANSLATE("Error creating link to \"%name\"."),
1753				ref.name);
1754
1755			if (loc && loc != (BPoint*)-1) {
1756				link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
1757					sizeof(PoseInfo));
1758			}
1759
1760			BNodeInfo nodeInfo(&link);
1761			nodeInfo.SetType(B_LINK_MIMETYPE);
1762			return B_OK;
1763		}
1764
1765		// if move is on same volume don't copy
1766		if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo
1767			&& moveMode != kDuplicateSelection) {
1768
1769			// for "Move" the size for status is always 1 - since file
1770			// size is irrelevant when simply moving to a new folder
1771			loopControl->UpdateStatus(ref.name, ref, 1);
1772			if (entry->IsDirectory())
1773				return RecursiveMove(entry, destDir, loopControl);
1774
1775			MoveError::FailOnError(entry->MoveTo(destDir, newName));
1776		} else {
1777			bool makeOriginalName = (moveMode == kDuplicateSelection);
1778			if (S_ISDIR(statbuf.st_mode)) {
1779				CopyFolder(entry, destDir, loopControl, loc, makeOriginalName,
1780					undo, moveMode == kMoveSelectionTo);
1781			} else {
1782				CopyFile(entry, &statbuf, destDir, loopControl, loc,
1783					makeOriginalName, undo);
1784				if (moveMode == kMoveSelectionTo)
1785					entry->Remove();
1786			}
1787		}
1788	} catch (status_t error) {
1789		// no alert, was already taken care of before
1790		return error;
1791	} catch (MoveError error) {
1792		BString errorString(B_TRANSLATE("Error moving \"%name\""));
1793		errorString.ReplaceFirst("%name", ref.name);
1794		BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"),
1795			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1796		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1797		alert->Go();
1798		return error.fError;
1799	} catch (FailWithAlert error) {
1800		BString buffer(error.fString);
1801		if (error.fName != NULL)
1802			buffer.ReplaceFirst("%name", error.fName);
1803		else
1804			buffer << error.fString;
1805
1806		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"),
1807			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1808		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1809		alert->Go();
1810
1811		return error.fError;
1812	}
1813
1814	return B_OK;
1815}
1816
1817
1818void
1819FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList)
1820{
1821	LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList,
1822		(BEntry*)NULL, pointList, kDuplicateSelection);
1823}
1824
1825
1826#if 0
1827status_t
1828FSCopyFolder(BEntry* srcEntry, BDirectory* destDir,
1829	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
1830{
1831	try
1832		CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName);
1833	catch (status_t error) {
1834		return error;
1835
1836	return B_OK;
1837}
1838#endif
1839
1840
1841status_t
1842FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes)
1843{
1844	char* buffer = new char[1024];
1845
1846	// copy the attributes
1847	srcNode->RewindAttrs();
1848	char name[256];
1849	while (srcNode->GetNextAttrName(name) == B_OK) {
1850		attr_info info;
1851		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1852			continue;
1853
1854		attr_info dest_info;
1855		if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1856			continue;
1857
1858		ssize_t bytes;
1859		ssize_t numToRead = (ssize_t)info.size;
1860		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1861			size_t chunkSize = (size_t)numToRead;
1862			if (chunkSize > 1024)
1863				chunkSize = 1024;
1864
1865			bytes = srcNode->ReadAttr(name, info.type, offset, buffer,
1866				chunkSize);
1867
1868			if (bytes <= 0)
1869				break;
1870
1871			destNode->WriteAttr(name, info.type, offset, buffer,
1872				(size_t)bytes);
1873
1874			numToRead -= bytes;
1875		}
1876	}
1877	delete[] buffer;
1878
1879	// copy the file stats
1880	struct stat srcStat;
1881	srcNode->GetStat(&srcStat);
1882	destNode->SetPermissions(srcStat.st_mode);
1883	destNode->SetOwner(srcStat.st_uid);
1884	destNode->SetGroup(srcStat.st_gid);
1885	if (copyTimes) {
1886		destNode->SetModificationTime(srcStat.st_mtime);
1887		destNode->SetCreationTime(srcStat.st_crtime);
1888	}
1889
1890	return B_OK;
1891}
1892
1893
1894#if 0
1895status_t
1896FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
1897	CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
1898{
1899	try {
1900		CopyFile(srcFile, srcStat, destDir, loopControl, loc,
1901			makeOriginalName);
1902	} catch (status_t error) {
1903		return error;
1904	}
1905
1906	return B_OK;
1907}
1908#endif
1909
1910
1911static status_t
1912MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo)
1913{
1914	BDirectory trash_dir;
1915	entry_ref ref;
1916	status_t result = entry->GetRef(&ref);
1917	if (result != B_OK)
1918		return result;
1919
1920	node_ref nodeRef;
1921	result = entry->GetNodeRef(&nodeRef);
1922	if (result != B_OK)
1923		return result;
1924
1925	StatStruct statbuf;
1926	result = entry->GetStat(&statbuf);
1927	if (entry->GetStat(&statbuf) != B_OK)
1928		return result;
1929
1930	// if it's a directory close the window and any child dir windows
1931	if (S_ISDIR(statbuf.st_mode)) {
1932		BDirectory dir(entry);
1933
1934		// if it's a volume, try to unmount
1935		if (dir.IsRootDirectory()) {
1936			BVolume	volume(nodeRef.device);
1937			BVolume	boot;
1938
1939			BVolumeRoster().GetBootVolume(&boot);
1940			if (volume == boot) {
1941				char name[B_FILE_NAME_LENGTH];
1942				volume.GetName(name);
1943				BString buffer(
1944					B_TRANSLATE("Cannot unmount the boot volume \"%name\"."));
1945				buffer.ReplaceFirst("%name", name);
1946				BAlert* alert = new BAlert("", buffer.String(),
1947					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1948					B_WARNING_ALERT);
1949				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1950				alert->Go();
1951			} else {
1952				BMessage message(kUnmountVolume);
1953				message.AddInt32("device_id", volume.Device());
1954				be_app->PostMessage(&message);
1955			}
1956			return B_OK;
1957		}
1958
1959		// get trash directory on same volume as item being moved
1960		result = FSGetTrashDir(&trash_dir, nodeRef.device);
1961		if (result != B_OK)
1962			return result;
1963
1964		// check hierarchy before moving
1965		BEntry trashEntry;
1966		trash_dir.GetEntry(&trashEntry);
1967
1968		if (dir == trash_dir || dir.Contains(&trashEntry)) {
1969			BAlert* alert = new BAlert("",
1970				B_TRANSLATE("You cannot put the selected item(s) "
1971					"into the trash."),
1972				B_TRANSLATE("OK"),
1973				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1974			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1975			alert->Go();
1976
1977			// return no error so we don't get two dialogs
1978			return B_OK;
1979		}
1980
1981		BMessage message(kCloseWindowAndChildren);
1982
1983		node_ref parentNode;
1984		parentNode.device = statbuf.st_dev;
1985		parentNode.node = statbuf.st_ino;
1986		message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref));
1987		be_app->PostMessage(&message);
1988	} else {
1989		// get trash directory on same volume as item being moved
1990		result = FSGetTrashDir(&trash_dir, nodeRef.device);
1991		if (result != B_OK)
1992			return result;
1993	}
1994
1995	// make sure name doesn't conflict with anything in trash already
1996	char name[B_FILE_NAME_LENGTH];
1997	strlcpy(name, ref.name, sizeof(name));
1998	if (trash_dir.Contains(name)) {
1999		BString suffix(" ");
2000		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
2001		FSMakeOriginalName(name, &trash_dir, suffix.String());
2002		undo.UpdateEntry(entry, name);
2003	}
2004
2005	BNode* src_node = 0;
2006	if (loc && loc != (BPoint*)-1
2007		&& (src_node = GetWritableNode(entry, &statbuf)) != 0) {
2008		trash_dir.GetStat(&statbuf);
2009		PoseInfo poseInfo;
2010		poseInfo.fInvisible = false;
2011		poseInfo.fInitedDirectory = statbuf.st_ino;
2012		poseInfo.fLocation = *loc;
2013		src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2014			sizeof(poseInfo));
2015		delete src_node;
2016	}
2017
2018	BNode node(entry);
2019	BPath path;
2020	// Get path of entry before it's moved to the trash
2021	// and write it to the file as an attribute
2022	if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) {
2023		BString originalPath(path.Path());
2024		node.WriteAttrString(kAttrOriginalPath, &originalPath);
2025	}
2026
2027	TrackerCopyLoopControl loopControl;
2028	MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo,
2029		&loopControl);
2030	return B_OK;
2031}
2032
2033
2034ConflictCheckResult
2035PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir,
2036	int32* collisionCount, uint32 moveMode)
2037{
2038	// count the number of name collisions in dest folder
2039	*collisionCount = 0;
2040
2041	int32 count = srcList->CountItems();
2042	for (int32 i = 0; i < count; i++) {
2043		entry_ref* srcRef = srcList->ItemAt(i);
2044		BEntry entry(srcRef);
2045		BDirectory parent;
2046		entry.GetParent(&parent);
2047
2048		if (parent != *destDir && destDir->Contains(srcRef->name))
2049			(*collisionCount)++;
2050	}
2051
2052	// prompt user only if there is more than one collision, otherwise the
2053	// single collision case will be handled as a "Prompt" case by CheckName
2054	if (*collisionCount > 1) {
2055		const char* verb = (moveMode == kMoveSelectionTo)
2056			? B_TRANSLATE("moving")	: B_TRANSLATE("copying");
2057		BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr));
2058		replaceMsg.ReplaceAll("%verb", verb);
2059
2060		BAlert* alert = new BAlert();
2061		alert->SetText(replaceMsg.String());
2062		alert->AddButton(B_TRANSLATE("Cancel"));
2063		alert->AddButton(B_TRANSLATE("Prompt"));
2064		alert->AddButton(B_TRANSLATE("Skip all"));
2065		alert->AddButton(B_TRANSLATE("Replace all"));
2066		alert->SetShortcut(0, B_ESCAPE);
2067		switch (alert->Go()) {
2068			case 0:
2069				return kCanceled;
2070
2071			case 1:
2072				// user selected "Prompt"
2073				return kPrompt;
2074
2075			case 2:
2076				// user selected "Skip all"
2077				return kSkipAll;
2078
2079			case 3:
2080				// user selected "Replace all"
2081				return kReplaceAll;
2082		}
2083	}
2084
2085	return kNoConflicts;
2086}
2087
2088
2089void
2090FileStatToString(StatStruct* stat, char* buffer, int32 length)
2091{
2092	tm timeData;
2093	localtime_r(&stat->st_mtime, &timeData);
2094
2095	BString size;
2096	static BStringFormat format(
2097		B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}"));
2098	format.Format(size, stat->st_size);
2099	uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String());
2100
2101	strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData);
2102}
2103
2104
2105status_t
2106CheckName(uint32 moveMode, const BEntry* sourceEntry,
2107	const BDirectory* destDir, bool multipleCollisions,
2108	ConflictCheckResult& conflictResolution)
2109{
2110	if (moveMode == kDuplicateSelection) {
2111		// when duplicating, we will never have a conflict
2112		return B_OK;
2113	}
2114
2115	// see if item already exists in destination dir
2116	const char* name = sourceEntry->Name();
2117	bool sourceIsDirectory = sourceEntry->IsDirectory();
2118
2119	BDirectory srcDirectory;
2120	if (sourceIsDirectory) {
2121		srcDirectory.SetTo(sourceEntry);
2122		BEntry destEntry;
2123		destDir->GetEntry(&destEntry);
2124
2125		if (moveMode != kCreateLink && moveMode != kCreateRelativeLink
2126			&& (srcDirectory == *destDir
2127				|| srcDirectory.Contains(&destEntry))) {
2128			BAlert* alert = new BAlert("",
2129				B_TRANSLATE("You can't move a folder into itself "
2130				"or any of its own sub-folders."), B_TRANSLATE("OK"),
2131				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2132			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2133			alert->Go();
2134			return B_ERROR;
2135		}
2136	}
2137
2138	if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink
2139		&& moveMode != kCreateRelativeLink) {
2140		BAlert* alert = new BAlert("",
2141			B_TRANSLATE("You can't move or copy the trash."),
2142			B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2143			B_WARNING_ALERT);
2144		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2145		alert->Go();
2146		return B_ERROR;
2147	}
2148
2149	BEntry entry;
2150	if (destDir->FindEntry(name, &entry) != B_OK) {
2151		// no conflict, return
2152		return B_OK;
2153	}
2154
2155	if (moveMode == kCreateLink	|| moveMode == kCreateRelativeLink) {
2156		// if we are creating link in the same directory, the conflict will
2157		// be handled later by giving the link a unique name
2158		sourceEntry->GetParent(&srcDirectory);
2159
2160		if (srcDirectory == *destDir)
2161			return B_OK;
2162	}
2163
2164	bool destIsDir = entry.IsDirectory();
2165	// be sure not to replace the parent directory of the item being moved
2166	if (destIsDir) {
2167		BDirectory targetDir(&entry);
2168		if (targetDir.Contains(sourceEntry)) {
2169			BAlert* alert = new BAlert("",
2170				B_TRANSLATE("You can't replace a folder "
2171				"with one of its sub-folders."),
2172				B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2173			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2174			alert->Go();
2175			return B_ERROR;
2176		}
2177	}
2178
2179	// ensure that the user isn't trying to replace a file with folder
2180	// or vice-versa
2181	if (moveMode != kCreateLink
2182		&& moveMode != kCreateRelativeLink
2183		&& destIsDir != sourceIsDirectory) {
2184		BAlert* alert = new BAlert("", sourceIsDirectory
2185			? B_TRANSLATE("You cannot replace a file with a folder or a "
2186				"symbolic link.")
2187			: B_TRANSLATE("You cannot replace a folder or a symbolic link "
2188				"with a file."), B_TRANSLATE("OK"),	0, 0, B_WIDTH_AS_USUAL,
2189			B_WARNING_ALERT);
2190		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2191		alert->Go();
2192		return B_ERROR;
2193	}
2194
2195	if (conflictResolution == kSkipAll)
2196		return B_ERROR;
2197
2198	if (conflictResolution != kReplaceAll) {
2199		// prompt user to determine whether to replace or not
2200		BString replaceMsg;
2201
2202		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2203			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr));
2204			replaceMsg.ReplaceFirst("%name", name);
2205		} else if (sourceEntry->IsDirectory()) {
2206			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr));
2207			replaceMsg.ReplaceFirst("%name", name);
2208			replaceMsg.ReplaceFirst("%verb",
2209				moveMode == kMoveSelectionTo
2210				? B_TRANSLATE("moving")
2211				: B_TRANSLATE("copying"));
2212		} else {
2213			char sourceBuffer[96], destBuffer[96];
2214			StatStruct statBuffer;
2215
2216			if (!sourceEntry->IsDirectory()
2217				&& sourceEntry->GetStat(&statBuffer) == B_OK) {
2218				FileStatToString(&statBuffer, sourceBuffer, 96);
2219			} else
2220				sourceBuffer[0] = '\0';
2221
2222			if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
2223				FileStatToString(&statBuffer, destBuffer, 96);
2224			else
2225				destBuffer[0] = '\0';
2226
2227			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr));
2228			replaceMsg.ReplaceAll("%name", name);
2229			replaceMsg.ReplaceFirst("%dest", destBuffer);
2230			replaceMsg.ReplaceFirst("%src", sourceBuffer);
2231			replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo
2232				? B_TRANSLATE("moving") : B_TRANSLATE("copying"));
2233		}
2234
2235		// special case single collision (don't need Replace All shortcut)
2236		BAlert* alert;
2237		if (multipleCollisions || sourceIsDirectory) {
2238			alert = new BAlert();
2239			alert->SetText(replaceMsg.String());
2240			alert->AddButton(B_TRANSLATE("Skip"));
2241			alert->AddButton(B_TRANSLATE("Skip all"));
2242			alert->AddButton(B_TRANSLATE("Replace"));
2243			alert->AddButton(B_TRANSLATE("Replace all"));
2244			switch (alert->Go()) {
2245				case 0:
2246					conflictResolution = kCanceled;
2247					return B_ERROR;
2248				case 1:
2249					conflictResolution = kSkipAll;
2250					return B_ERROR;
2251				case 2:
2252					conflictResolution = kReplace;
2253					break;
2254				case 3:
2255					conflictResolution = kReplaceAll;
2256					break;
2257			}
2258		} else {
2259			alert = new BAlert("", replaceMsg.String(),
2260				B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2261			alert->SetShortcut(0, B_ESCAPE);
2262			switch (alert->Go()) {
2263				case 0:
2264					conflictResolution = kCanceled;
2265					return B_ERROR;
2266				case 1:
2267					conflictResolution = kReplace;
2268					break;
2269			}
2270		}
2271	}
2272
2273	// delete destination item
2274	if (destIsDir)
2275		return B_OK;
2276
2277	status_t status = entry.Remove();
2278	if (status != B_OK) {
2279		BString error(B_TRANSLATE("There was a problem trying to replace "
2280			"\"%name\". The item might be open or busy."));
2281		error.ReplaceFirst("%name", name);
2282		BAlert* alert = new BAlert("", error.String(),
2283			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2284		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2285		alert->Go();
2286	}
2287
2288	return status;
2289}
2290
2291
2292status_t
2293FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl,
2294	bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus)
2295{
2296	BDirectory dir(dirEntry);
2297
2298	// loop through everything in folder and delete it, skipping trouble files
2299	BEntry entry;
2300	while (dir.GetNextEntry(&entry) == B_OK) {
2301		entry_ref ref;
2302		entry.GetRef(&ref);
2303
2304		if (loopControl->CheckUserCanceled())
2305			return kTrashCanceled;
2306
2307		status_t status;
2308
2309		if (entry.IsDirectory())
2310			status = FSDeleteFolder(&entry, loopControl, updateStatus, true,
2311				upateFileNameInStatus);
2312		else {
2313			status = entry.Remove();
2314			if (updateStatus) {
2315				loopControl->UpdateStatus(upateFileNameInStatus ? ref.name
2316					: "", ref, 1, true);
2317			}
2318		}
2319
2320		if (status == kTrashCanceled)
2321			return kTrashCanceled;
2322
2323		if (status != B_OK) {
2324			loopControl->FileError(B_TRANSLATE_NOCOLLECT(
2325					kFileDeleteErrorString), ref.name, status, false);
2326		}
2327	}
2328
2329	if (loopControl->CheckUserCanceled())
2330		return kTrashCanceled;
2331
2332	entry_ref ref;
2333	dirEntry->GetRef(&ref);
2334
2335	if (updateStatus && deleteTopDir)
2336		loopControl->UpdateStatus(NULL, ref, 1);
2337
2338	if (deleteTopDir)
2339		return dirEntry->Remove();
2340
2341	return B_OK;
2342}
2343
2344
2345void
2346FSMakeOriginalName(BString &string, const BDirectory* destDir,
2347	const char* suffix)
2348{
2349	if (!destDir->Contains(string.String()))
2350		return;
2351
2352	FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
2353		const_cast<BDirectory*>(destDir), suffix ? suffix : " copy");
2354	string.UnlockBuffer();
2355}
2356
2357
2358void
2359FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix)
2360{
2361	char		root[B_FILE_NAME_LENGTH];
2362	char		copybase[B_FILE_NAME_LENGTH];
2363	char		temp_name[B_FILE_NAME_LENGTH + 10];
2364	int32		fnum;
2365
2366	// is this name already original?
2367	if (!destDir->Contains(name))
2368		return;
2369
2370	// Determine if we're copying a 'copy'. This algorithm isn't perfect.
2371	// If you're copying a file whose REAL name ends with 'copy' then
2372	// this method will return "<filename> 1", not "<filename> copy"
2373
2374	// However, it will correctly handle file that contain 'copy'
2375	// elsewhere in their name.
2376
2377	bool copycopy = false;		// are we copying a copy?
2378	int32 len = (int32)strlen(name);
2379	char* p = name + len - 1;	// get pointer to end os name
2380
2381	// eat up optional numbers (if were copying "<filename> copy 34")
2382	while ((p > name) && isdigit(*p))
2383		p--;
2384
2385	// eat up optional spaces
2386	while ((p > name) && isspace(*p))
2387		p--;
2388
2389	// now look for the phrase " copy"
2390	if (p > name) {
2391		// p points to the last char of the word. For example, 'y' in 'copy'
2392
2393		if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
2394			// we found 'copy' in the right place.
2395			// so truncate after 'copy'
2396			*(p + 1) = '\0';
2397			copycopy = true;
2398
2399			// save the 'root' name of the file, for possible later use.
2400			// that is copy everything but trailing " copy". Need to
2401			// NULL terminate after copy
2402			strncpy(root, name, (uint32)((p - name) - 4));
2403			root[(p - name) - 4] = '\0';
2404		}
2405	}
2406
2407	if (!copycopy) {
2408		// The name can't be longer than B_FILE_NAME_LENGTH.
2409		// The algoritm adds " copy XX" to the name. That's 8 characters.
2410		// B_FILE_NAME_LENGTH already accounts for NULL termination so we
2411		// don't need to save an extra char at the end.
2412		if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
2413			// name is too long - truncate it!
2414			name[B_FILE_NAME_LENGTH - 8] = '\0';
2415		}
2416
2417		strcpy(root, name);		// save root name
2418		strcat(name, suffix);
2419	}
2420
2421	strcpy(copybase, name);
2422
2423	// if name already exists then add a number
2424	fnum = 1;
2425	strcpy(temp_name, name);
2426	while (destDir->Contains(temp_name)) {
2427		snprintf(temp_name, sizeof(temp_name), "%s %" B_PRId32, copybase, ++fnum);
2428
2429		if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) {
2430			// The name has grown too long. Maybe we just went from
2431			// "<filename> copy 9" to "<filename> copy 10" and that extra
2432			// character was too much. The solution is to further
2433			// truncate the 'root' name and continue.
2434			// ??? should we reset fnum or not ???
2435			root[strlen(root) - 1] = '\0';
2436			snprintf(temp_name, sizeof(temp_name), "%s%s %" B_PRId32, root, suffix, fnum);
2437		}
2438	}
2439
2440	ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1)));
2441	strcpy(name, temp_name);
2442}
2443
2444
2445status_t
2446FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl,
2447	BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount)
2448{
2449	dir->Rewind();
2450	BEntry entry;
2451	while (dir->GetNextEntry(&entry) == B_OK) {
2452		// be sure window hasn't closed
2453		if (window && window->StopCalc())
2454			return B_OK;
2455
2456		if (loopControl->CheckUserCanceled())
2457			return kUserCanceled;
2458
2459		StatStruct statbuf;
2460		status_t status = entry.GetStat(&statbuf);
2461		if (status != B_OK)
2462			return status;
2463
2464		(*_runningSize) += statbuf.st_blocks* 512;
2465
2466		if (S_ISDIR(statbuf.st_mode)) {
2467			BDirectory subdir(&entry);
2468			(*_dirCount)++;
2469			status = FSRecursiveCalcSize(window, loopControl, &subdir,
2470				_runningSize, _fileCount, _dirCount);
2471			if (status != B_OK)
2472				return status;
2473		} else
2474			(*_fileCount)++;
2475	}
2476	return B_OK;
2477}
2478
2479
2480status_t
2481CalcItemsAndSize(CopyLoopControl* loopControl,
2482	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
2483	off_t* totalSize)
2484{
2485	int32 fileCount = 0;
2486	int32 dirCount = 0;
2487
2488	// check block size for sanity
2489	if (blockSize < 0) {
2490		// This would point at an error to retrieve the block size from
2491		// the target volume. The code below cannot be used, it is only
2492		// meant to get the block size when item operations happen on
2493		// the source volume.
2494		blockSize = 2048;
2495	} else if (blockSize < 1024) {
2496		blockSize = 1024;
2497		if (entry_ref* ref = refList->ItemAt(0)) {
2498			// TODO: This assumes all entries in the list share the same
2499			// volume...
2500			BVolume volume(ref->device);
2501			if (volume.InitCheck() == B_OK)
2502				blockSize = volume.BlockSize();
2503		}
2504	}
2505	// File systems like ReiserFS may advertize a large block size, but
2506	// stuff is still packed into blocks, so clamp maximum block size.
2507	if (blockSize > 8192)
2508		blockSize = 8192;
2509
2510	int32 num_items = refList->CountItems();
2511	for (int32 i = 0; i < num_items; i++) {
2512		entry_ref* ref = refList->ItemAt(i);
2513		BEntry entry(ref);
2514		StatStruct statbuf;
2515		entry.GetStat(&statbuf);
2516
2517		if (loopControl->CheckUserCanceled())
2518			return kUserCanceled;
2519
2520		if (S_ISDIR(statbuf.st_mode)) {
2521			BDirectory dir(&entry);
2522			dirCount++;
2523			(*totalSize) += blockSize;
2524			status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2525				totalSize, &fileCount, &dirCount);
2526			if (result != B_OK)
2527				return result;
2528		} else {
2529			fileCount++;
2530			(*totalSize) += statbuf.st_size + blockSize;
2531		}
2532	}
2533
2534	*totalCount += (fileCount + dirCount);
2535	return B_OK;
2536}
2537
2538
2539status_t
2540FSGetTrashDir(BDirectory* trashDir, dev_t dev)
2541{
2542	if (trashDir == NULL)
2543		return B_BAD_VALUE;
2544
2545	BVolume volume(dev);
2546	status_t result = volume.InitCheck();
2547	if (result != B_OK)
2548		return result;
2549
2550	BPath path;
2551	result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2552	if (result != B_OK)
2553		return result;
2554
2555	result = trashDir->SetTo(path.Path());
2556	if (result != B_OK) {
2557		// Trash directory does not exist yet, create it.
2558		result = create_directory(path.Path(), 0755);
2559		if (result != B_OK)
2560			return result;
2561
2562		result = trashDir->SetTo(path.Path());
2563		if (result != B_OK)
2564			return result;
2565
2566		// make Trash directory invisible
2567		StatStruct sbuf;
2568		trashDir->GetStat(&sbuf);
2569
2570		PoseInfo poseInfo;
2571		poseInfo.fInvisible = true;
2572		poseInfo.fInitedDirectory = sbuf.st_ino;
2573		trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2574			sizeof(PoseInfo));
2575	}
2576
2577	// set Trash icons (if they haven't already been set)
2578	attr_info attrInfo;
2579	size_t size;
2580	const void* data;
2581	if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2582		data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2583		if (data != NULL)
2584			trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2585	}
2586
2587	if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2588		data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2589		if (data != NULL)
2590			trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2591	}
2592
2593	if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2594		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2595			R_TrashIcon, &size);
2596		if (data != NULL)
2597			trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2598	}
2599
2600	return B_OK;
2601}
2602
2603
2604status_t
2605FSGetDeskDir(BDirectory* deskDir)
2606{
2607	if (deskDir == NULL)
2608		return B_BAD_VALUE;
2609
2610	BPath path;
2611	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2612	if (result != B_OK)
2613		return result;
2614
2615	result = deskDir->SetTo(path.Path());
2616	if (result != B_OK)
2617		return result;
2618
2619	// set Desktop icons (if they haven't already been set)
2620	attr_info attrInfo;
2621	size_t size;
2622	const void* data;
2623	if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2624		data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size);
2625		if (data != NULL)
2626			deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2627	}
2628
2629	if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2630		data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size);
2631		if (data != NULL)
2632			deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2633	}
2634
2635	if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2636		data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2637			R_DeskIcon, &size);
2638		if (data != NULL)
2639			deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2640	}
2641
2642	return B_OK;
2643}
2644
2645
2646status_t
2647FSGetBootDeskDir(BDirectory* deskDir)
2648{
2649	BVolume bootVolume;
2650	BVolumeRoster().GetBootVolume(&bootVolume);
2651	BPath path;
2652
2653	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2654		&bootVolume);
2655	if (result != B_OK)
2656		return result;
2657
2658	return deskDir->SetTo(path.Path());
2659}
2660
2661
2662static bool
2663FSIsDirFlavor(const BEntry* entry, directory_which directoryType)
2664{
2665	StatStruct dir_stat;
2666	StatStruct entry_stat;
2667	BVolume volume;
2668	BPath path;
2669
2670	if (entry->GetStat(&entry_stat) != B_OK)
2671		return false;
2672
2673	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2674		return false;
2675
2676	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2677		return false;
2678
2679	stat(path.Path(), &dir_stat);
2680
2681	return dir_stat.st_ino == entry_stat.st_ino
2682		&& dir_stat.st_dev == entry_stat.st_dev;
2683}
2684
2685
2686bool
2687FSIsPrintersDir(const BEntry* entry)
2688{
2689	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2690}
2691
2692
2693bool
2694FSIsTrashDir(const BEntry* entry)
2695{
2696	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2697}
2698
2699
2700bool
2701FSIsDeskDir(const BEntry* entry)
2702{
2703	BPath path;
2704	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2705	if (result != B_OK)
2706		return false;
2707
2708	BEntry entryToCompare(path.Path());
2709	return entryToCompare == *entry;
2710}
2711
2712
2713bool
2714FSIsHomeDir(const BEntry* entry)
2715{
2716	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2717}
2718
2719
2720bool
2721FSIsRootDir(const BEntry* entry)
2722{
2723	BPath path(entry);
2724	return path == "/";
2725}
2726
2727
2728bool
2729DirectoryMatchesOrContains(const BEntry* entry, directory_which which)
2730{
2731	BPath path;
2732	if (find_directory(which, &path, false, NULL) != B_OK)
2733		return false;
2734
2735	BEntry dirEntry(path.Path());
2736	if (dirEntry.InitCheck() != B_OK)
2737		return false;
2738
2739	if (dirEntry == *entry)
2740		// root level match
2741		return true;
2742
2743	BDirectory dir(&dirEntry);
2744	return dir.Contains(entry);
2745}
2746
2747
2748bool
2749DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath,
2750	directory_which which)
2751{
2752	BPath path;
2753	if (find_directory(which, &path, false, NULL) != B_OK)
2754		return false;
2755
2756	path.Append(additionalPath);
2757	BEntry dirEntry(path.Path());
2758	if (dirEntry.InitCheck() != B_OK)
2759		return false;
2760
2761	if (dirEntry == *entry)
2762		// root level match
2763		return true;
2764
2765	BDirectory dir(&dirEntry);
2766	return dir.Contains(entry);
2767}
2768
2769
2770bool
2771DirectoryMatches(const BEntry* entry, directory_which which)
2772{
2773	BPath path;
2774	if (find_directory(which, &path, false, NULL) != B_OK)
2775		return false;
2776
2777	BEntry dirEntry(path.Path());
2778	if (dirEntry.InitCheck() != B_OK)
2779		return false;
2780
2781	return dirEntry == *entry;
2782}
2783
2784
2785bool
2786DirectoryMatches(const BEntry* entry, const char* additionalPath,
2787	directory_which which)
2788{
2789	BPath path;
2790	if (find_directory(which, &path, false, NULL) != B_OK)
2791		return false;
2792
2793	path.Append(additionalPath);
2794	BEntry dirEntry(path.Path());
2795	if (dirEntry.InitCheck() != B_OK)
2796		return false;
2797
2798	return dirEntry == *entry;
2799}
2800
2801
2802extern status_t
2803FSFindTrackerSettingsDir(BPath* path, bool autoCreate)
2804{
2805	status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path,
2806		autoCreate);
2807	if (result != B_OK)
2808		return result;
2809
2810	path->Append("Tracker");
2811
2812	return mkdir(path->Path(), 0777) ? B_OK : errno;
2813}
2814
2815
2816bool
2817FSInTrashDir(const entry_ref* ref)
2818{
2819	BEntry entry(ref);
2820	if (entry.InitCheck() != B_OK)
2821		return false;
2822
2823	BDirectory trashDir;
2824	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2825		return false;
2826
2827	return trashDir.Contains(&entry);
2828}
2829
2830
2831void
2832FSEmptyTrash()
2833{
2834	if (find_thread("_tracker_empty_trash_") != B_OK) {
2835		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2836			B_NORMAL_PRIORITY, NULL));
2837	}
2838}
2839
2840
2841status_t
2842empty_trash(void*)
2843{
2844	// empty trash on all mounted volumes
2845	status_t status = B_OK;
2846
2847	TrackerCopyLoopControl loopControl(kTrashState);
2848
2849	// calculate the sum total of all items on all volumes in trash
2850	BObjectList<entry_ref> srcList;
2851	int32 totalCount = 0;
2852	off_t totalSize = 0;
2853
2854	BVolumeRoster volumeRoster;
2855	BVolume volume;
2856	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2857		if (volume.IsReadOnly() || !volume.IsPersistent())
2858			continue;
2859
2860		BDirectory trashDirectory;
2861		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2862			continue;
2863
2864		BEntry entry;
2865		trashDirectory.GetEntry(&entry);
2866
2867		entry_ref ref;
2868		entry.GetRef(&ref);
2869		srcList.AddItem(&ref);
2870		status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
2871			&totalCount, &totalSize);
2872		if (status != B_OK)
2873			break;
2874
2875		srcList.MakeEmpty();
2876
2877		// don't count trash directory itself
2878		totalCount--;
2879	}
2880
2881	if (status == B_OK) {
2882		loopControl.Init(totalCount, totalCount);
2883
2884		volumeRoster.Rewind();
2885		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2886			if (volume.IsReadOnly() || !volume.IsPersistent())
2887				continue;
2888
2889			BDirectory trashDirectory;
2890			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2891				continue;
2892
2893			BEntry entry;
2894			trashDirectory.GetEntry(&entry);
2895			status = FSDeleteFolder(&entry, &loopControl, true, false);
2896		}
2897	}
2898
2899	if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) {
2900		BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"),
2901			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2902			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2903			alert->Go();
2904	}
2905
2906	return B_OK;
2907}
2908
2909
2910status_t
2911_DeleteTask(BObjectList<entry_ref>* list, bool confirm)
2912{
2913	if (confirm) {
2914		bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
2915
2916		if (!dontMoveToTrash) {
2917			BAlert* alert = new BAlert("",
2918				B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2919				B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
2920				B_TRANSLATE("Delete"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2921				B_WARNING_ALERT);
2922
2923			alert->SetShortcut(0, B_ESCAPE);
2924			alert->SetShortcut(1, 'm');
2925			alert->SetShortcut(2, 'd');
2926
2927			switch (alert->Go()) {
2928				case 0:
2929					delete list;
2930					return B_OK;
2931				case 1:
2932					FSMoveToTrash(list, NULL, false);
2933					return B_OK;
2934			}
2935		} else {
2936			BAlert* alert = new BAlert("",
2937				B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2938				B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"), NULL,
2939				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
2940
2941			alert->SetShortcut(0, B_ESCAPE);
2942			alert->SetShortcut(1, 'd');
2943
2944			if (!alert->Go()) {
2945				delete list;
2946				return B_OK;
2947			}
2948		}
2949	}
2950
2951	TrackerCopyLoopControl loopControl(kDeleteState);
2952
2953	// calculate the sum total of all items on all volumes in trash
2954	int32 totalItems = 0;
2955	int64 totalSize = 0;
2956
2957	status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2958		&totalSize);
2959	if (status == B_OK) {
2960		loopControl.Init(totalItems, totalItems);
2961
2962		int32 count = list->CountItems();
2963		for (int32 index = 0; index < count; index++) {
2964			entry_ref ref(*list->ItemAt(index));
2965			BEntry entry(&ref);
2966			loopControl.UpdateStatus(ref.name, ref, 1, true);
2967			if (entry.IsDirectory())
2968				status = FSDeleteFolder(&entry, &loopControl, true, true, true);
2969			else
2970				status = entry.Remove();
2971		}
2972
2973		if (status != kTrashCanceled && status != kUserCanceled
2974			&& status != B_OK) {
2975			BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"),
2976				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
2977				B_WARNING_ALERT);
2978			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2979			alert->Go();
2980		}
2981	}
2982
2983	delete list;
2984
2985	return B_OK;
2986}
2987
2988status_t
2989FSRecursiveCreateFolder(BPath path)
2990{
2991	BEntry entry(path.Path());
2992	if (entry.InitCheck() != B_OK) {
2993		BPath parentPath;
2994		status_t err = path.GetParent(&parentPath);
2995		if (err != B_OK)
2996			return err;
2997
2998		err = FSRecursiveCreateFolder(parentPath);
2999		if (err != B_OK)
3000			return err;
3001	}
3002
3003	entry.SetTo(path.Path());
3004	if (entry.Exists())
3005		return B_FILE_EXISTS;
3006
3007	BDirectory parent;
3008	entry.GetParent(&parent);
3009	parent.CreateDirectory(entry.Name(), NULL);
3010
3011	return B_OK;
3012}
3013
3014status_t
3015_RestoreTask(BObjectList<entry_ref>* list)
3016{
3017	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
3018
3019	// calculate the sum total of all items that will be restored
3020	int32 totalItems = 0;
3021	int64 totalSize = 0;
3022
3023	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3024		&totalSize);
3025	if (err == B_OK) {
3026		loopControl.Init(totalItems, totalItems);
3027
3028		int32 count = list->CountItems();
3029		for (int32 index = 0; index < count; index++) {
3030			entry_ref ref(*list->ItemAt(index));
3031			BEntry entry(&ref);
3032			BPath originalPath;
3033
3034			loopControl.UpdateStatus(ref.name, ref, 1, true);
3035
3036			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
3037				continue;
3038
3039			BEntry originalEntry(originalPath.Path());
3040			BPath parentPath;
3041			err = originalPath.GetParent(&parentPath);
3042			if (err != B_OK)
3043				continue;
3044			BEntry parentEntry(parentPath.Path());
3045
3046			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
3047				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
3048					originalEntry.SetTo(originalPath.Path());
3049					if (entry.InitCheck() != B_OK)
3050						continue;
3051				}
3052			}
3053
3054			if (!originalEntry.Exists()) {
3055				BDirectory dir(parentPath.Path());
3056				if (dir.InitCheck() == B_OK) {
3057					const char* leafName = originalEntry.Name();
3058					if (entry.MoveTo(&dir, leafName) == B_OK) {
3059						BNode node(&entry);
3060						if (node.InitCheck() == B_OK)
3061							node.RemoveAttr(kAttrOriginalPath);
3062					}
3063				}
3064			}
3065
3066			err = loopControl.CheckUserCanceled();
3067			if (err != B_OK)
3068				break;
3069		}
3070	}
3071
3072	delete list;
3073
3074	return err;
3075}
3076
3077void
3078FSCreateTrashDirs()
3079{
3080	BVolume volume;
3081	BVolumeRoster roster;
3082
3083	roster.Rewind();
3084	while (roster.GetNextVolume(&volume) == B_OK) {
3085		if (volume.IsReadOnly() || !volume.IsPersistent())
3086			continue;
3087
3088		BDirectory trashDir;
3089		FSGetTrashDir(&trashDir, volume.Device());
3090	}
3091}
3092
3093
3094status_t
3095FSCreateNewFolder(const entry_ref* ref)
3096{
3097	node_ref node;
3098	node.device = ref->device;
3099	node.node = ref->directory;
3100
3101	BDirectory dir(&node);
3102	status_t result = dir.InitCheck();
3103	if (result != B_OK)
3104		return result;
3105
3106	// ToDo: is that really necessary here?
3107	BString name(ref->name);
3108	FSMakeOriginalName(name, &dir, "-");
3109
3110	BDirectory newDir;
3111	result = dir.CreateDirectory(name.String(), &newDir);
3112	if (result != B_OK)
3113		return result;
3114
3115	BNodeInfo nodeInfo(&newDir);
3116	nodeInfo.SetType(B_DIR_MIMETYPE);
3117
3118	return result;
3119}
3120
3121
3122status_t
3123FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef,
3124	node_ref* newNode)
3125{
3126	BDirectory dir(dirNode);
3127	status_t result = dir.InitCheck();
3128	if (result == B_OK) {
3129		char name[B_FILE_NAME_LENGTH];
3130		strlcpy(name, B_TRANSLATE("New folder"), sizeof(name));
3131
3132		int32 fnum = 1;
3133		while (dir.Contains(name)) {
3134			// if base name already exists then add a number
3135			// ToDo:
3136			// move this logic ot FSMakeOriginalName
3137			if (++fnum > 9) {
3138				snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"),
3139					fnum);
3140			} else {
3141				snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"),
3142					fnum);
3143			}
3144		}
3145
3146		BDirectory newDir;
3147		result = dir.CreateDirectory(name, &newDir);
3148		if (result == B_OK) {
3149			BEntry entry;
3150			newDir.GetEntry(&entry);
3151			entry.GetRef(newRef);
3152			entry.GetNodeRef(newNode);
3153
3154			BNodeInfo nodeInfo(&newDir);
3155			nodeInfo.SetType(B_DIR_MIMETYPE);
3156
3157			// add undo item
3158			NewFolderUndo undo(*newRef);
3159			return B_OK;
3160		}
3161	}
3162
3163	BAlert* alert = new BAlert("",
3164		B_TRANSLATE("Sorry, could not create a new folder."),
3165		B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3166	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3167	alert->Go();
3168	return result;
3169}
3170
3171
3172ReadAttrResult
3173ReadAttr(const BNode* node, const char* hostAttrName,
3174	const char* foreignAttrName, type_code type, off_t offset, void* buffer,
3175	size_t length, void (*swapFunc)(void*), bool isForeign)
3176{
3177	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3178			length) == (ssize_t)length) {
3179		return kReadAttrNativeOK;
3180	}
3181
3182	// PRINT(("trying %s\n", foreignAttrName));
3183	// try the other endianness
3184	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3185			!= (ssize_t)length) {
3186		return kReadAttrFailed;
3187	}
3188
3189	// PRINT(("got %s\n", foreignAttrName));
3190	if (!swapFunc)
3191		return kReadAttrForeignOK;
3192
3193	(swapFunc)(buffer);
3194		// run the endian swapper
3195
3196	return kReadAttrForeignOK;
3197}
3198
3199
3200ReadAttrResult
3201GetAttrInfo(const BNode* node, const char* hostAttrName,
3202	const char* foreignAttrName, type_code* type, size_t* size)
3203{
3204	attr_info info;
3205
3206	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3207		if (type)
3208			*type = info.type;
3209		if (size)
3210			*size = (size_t)info.size;
3211
3212		return kReadAttrNativeOK;
3213	}
3214
3215	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3216		if (type)
3217			*type = info.type;
3218		if (size)
3219			*size = (size_t)info.size;
3220
3221		return kReadAttrForeignOK;
3222	}
3223	return kReadAttrFailed;
3224}
3225
3226
3227status_t
3228FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref)
3229{
3230	node_ref nodeRef;
3231	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3232		if (VirtualDirectoryManager* manager
3233				= VirtualDirectoryManager::Instance()) {
3234			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3235			if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref,
3236					nodeRef)) {
3237				return B_OK;
3238			}
3239		}
3240	}
3241
3242	status_t error;
3243	BDirectory parent;
3244	BEntry parentEntry;
3245	if ((error = entry.GetParent(&parent)) != B_OK
3246		|| (error = parent.GetEntry(&parentEntry)) != B_OK
3247		|| (error = parentEntry.GetRef(&_ref)) != B_OK) {
3248		return error;
3249	}
3250
3251	return B_OK;
3252}
3253
3254
3255status_t
3256FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry)
3257{
3258	node_ref nodeRef;
3259	if (entry.GetNodeRef(&nodeRef) == B_OK) {
3260		if (VirtualDirectoryManager* manager
3261				= VirtualDirectoryManager::Instance()) {
3262			AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3263			entry_ref parentRef;
3264			if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef,
3265					nodeRef)) {
3266				return _entry.SetTo(&parentRef);
3267			}
3268		}
3269	}
3270
3271	return entry.GetParent(&_entry);
3272}
3273
3274
3275status_t
3276FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node)
3277{
3278	entry_ref ref;
3279	status_t error = FSGetParentVirtualDirectoryAware(entry, ref);
3280	if (error == B_OK)
3281		error = _node.SetTo(&ref);
3282	return error;
3283}
3284
3285
3286// launching code
3287
3288static status_t
3289TrackerOpenWith(const BMessage* refs)
3290{
3291	BMessage clone(*refs);
3292
3293	ASSERT(dynamic_cast<TTracker*>(be_app) != NULL);
3294	ASSERT(clone.what != 0);
3295
3296	clone.AddInt32("launchUsingSelector", 0);
3297	// runs the Open With window
3298	be_app->PostMessage(&clone);
3299
3300	return B_OK;
3301}
3302
3303
3304static void
3305AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on),
3306	const entry_ref* appRef, const BMessage* refs, bool openWithOK)
3307{
3308	BMessage* task = new BMessage;
3309	task->AddPointer("function", (void*)func);
3310	task->AddMessage("refs", refs);
3311	task->AddBool("openWithOK", openWithOK);
3312	if (appRef != NULL)
3313		task->AddRef("appRef", appRef);
3314
3315	extern BLooper* gLaunchLooper;
3316	gLaunchLooper->PostMessage(task);
3317}
3318
3319
3320static bool
3321SniffIfGeneric(const entry_ref* ref)
3322{
3323	BNode node(ref);
3324	char type[B_MIME_TYPE_LENGTH];
3325	BNodeInfo info(&node);
3326	if (info.GetType(type) == B_OK
3327		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3328		// already has a type and it's not octet stream
3329		return false;
3330	}
3331
3332	BPath path(ref);
3333	if (path.Path()) {
3334		// force a mimeset
3335		node.RemoveAttr(kAttrMIMEType);
3336		update_mime_info(path.Path(), 0, 1, 1);
3337	}
3338
3339	return true;
3340}
3341
3342
3343static void
3344SniffIfGeneric(const BMessage* refs)
3345{
3346	entry_ref ref;
3347	for (int32 index = 0; ; index++) {
3348		if (refs->FindRef("refs", index, &ref) != B_OK)
3349			break;
3350		SniffIfGeneric(&ref);
3351	}
3352}
3353
3354
3355static void
3356_TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs,
3357	bool openWithOK)
3358{
3359	team_id team;
3360
3361	status_t error = B_ERROR;
3362	BString alertString;
3363
3364	for (int32 mimesetIt = 0; ; mimesetIt++) {
3365		error = be_roster->Launch(appRef, refs, &team);
3366		if (error == B_ALREADY_RUNNING)
3367			// app already running, not really an error
3368			error = B_OK;
3369
3370		if (error == B_OK)
3371			break;
3372
3373		if (mimesetIt > 0)
3374			break;
3375
3376		// failed to open, try mimesetting the refs and launching again
3377		SniffIfGeneric(refs);
3378	}
3379
3380	if (error == B_OK) {
3381		// close possible parent window, if specified
3382		const node_ref* nodeToClose = 0;
3383		ssize_t numBytes;
3384		if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3385				(const void**)&nodeToClose, &numBytes) == B_OK
3386			&& nodeToClose != NULL) {
3387			TTracker* tracker = dynamic_cast<TTracker*>(be_app);
3388			if (tracker != NULL)
3389				tracker->CloseParent(*nodeToClose);
3390		}
3391	} else {
3392		alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3393		alertString.ReplaceFirst("%name", appRef->name);
3394		alertString.ReplaceFirst("%error", strerror(error));
3395		if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) {
3396			alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3397			BAlert* alert = new BAlert("", alertString.String(),
3398				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3399				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3400			alert->SetShortcut(0, B_ESCAPE);
3401			if (alert->Go() == 1)
3402				error = TrackerOpenWith(refs);
3403		} else {
3404			BAlert* alert = new BAlert("", alertString.String(),
3405				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3406				B_WARNING_ALERT);
3407			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3408			alert->Go();
3409		}
3410	}
3411}
3412
3413extern "C" char** environ;
3414
3415
3416static status_t
3417LoaderErrorDetails(const entry_ref* app, BString &details)
3418{
3419	BPath path;
3420	BEntry appEntry(app, true);
3421
3422	status_t result = appEntry.GetPath(&path);
3423	if (result != B_OK)
3424		return result;
3425
3426	char* argv[2] = { const_cast<char*>(path.Path()), 0};
3427
3428	port_id errorPort = create_port(1, "Tracker loader error");
3429
3430	// count environment variables
3431	int32 envCount = 0;
3432	while (environ[envCount] != NULL)
3433		envCount++;
3434
3435	char** flatArgs = NULL;
3436	size_t flatArgsSize;
3437	result = __flatten_process_args((const char**)argv, 1,
3438		environ, &envCount, argv[0], &flatArgs, &flatArgsSize);
3439	if (result != B_OK)
3440		return result;
3441
3442	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3443		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3444	if (result == B_OK) {
3445		// we weren't supposed to be able to start the application...
3446		return B_ERROR;
3447	}
3448
3449	// read error message from port and construct details string
3450
3451	ssize_t bufferSize;
3452
3453	do {
3454		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3455	} while (bufferSize == B_INTERRUPTED);
3456
3457	if (bufferSize <= B_OK) {
3458		delete_port(errorPort);
3459		return bufferSize;
3460	}
3461
3462	uint8* buffer = (uint8*)malloc(bufferSize);
3463	if (buffer == NULL) {
3464		delete_port(errorPort);
3465		return B_NO_MEMORY;
3466	}
3467
3468	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3469		B_RELATIVE_TIMEOUT, 0);
3470	delete_port(errorPort);
3471
3472	if (bufferSize < B_OK) {
3473		free(buffer);
3474		return bufferSize;
3475	}
3476
3477	BMessage message;
3478	result = message.Unflatten((const char*)buffer);
3479	free(buffer);
3480
3481	if (result != B_OK)
3482		return result;
3483
3484	int32 errorCode = B_ERROR;
3485	result = message.FindInt32("error", &errorCode);
3486	if (result != B_OK)
3487		return result;
3488
3489	const char* detailName = NULL;
3490	switch (errorCode) {
3491		case B_MISSING_LIBRARY:
3492			detailName = "missing library";
3493			break;
3494
3495		case B_MISSING_SYMBOL:
3496			detailName = "missing symbol";
3497			break;
3498	}
3499
3500	if (detailName == NULL)
3501		return B_ERROR;
3502
3503	const char* detail;
3504	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3505			i++) {
3506		if (i > 0)
3507			details += ", ";
3508		details += detail;
3509	}
3510
3511	return B_OK;
3512}
3513
3514
3515static void
3516_TrackerLaunchDocuments(const entry_ref*, const BMessage* refs,
3517	bool openWithOK)
3518{
3519	if (refs == NULL)
3520		return;
3521
3522	BMessage copyOfRefs(*refs);
3523
3524	entry_ref documentRef;
3525	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) {
3526		// nothing to launch, we are done
3527		return;
3528	}
3529
3530	status_t error = B_ERROR;
3531	entry_ref app;
3532	BMessage* refsToPass = NULL;
3533	BString alertString;
3534	const char* alternative = 0;
3535
3536	for (int32 mimesetIt = 0; ; mimesetIt++) {
3537		alertString = "";
3538		error = be_roster->FindApp(&documentRef, &app);
3539
3540		if (error != B_OK && mimesetIt == 0) {
3541			SniffIfGeneric(&copyOfRefs);
3542			continue;
3543		}
3544
3545		if (error != B_OK) {
3546			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3547				"open \"%name\" (%error). "));
3548			alertString.ReplaceFirst("%name", documentRef.name);
3549			alertString.ReplaceFirst("%error", strerror(error));
3550			if (openWithOK)
3551				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3552
3553			break;
3554		} else {
3555			BEntry appEntry(&app, true);
3556			for (int32 index = 0;;) {
3557				// remove the app itself from the refs received so we don't
3558				// try to open ourselves
3559				entry_ref ref;
3560				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3561					break;
3562
3563				// deal with symlinks properly
3564				BEntry documentEntry(&ref, true);
3565				if (appEntry == documentEntry) {
3566					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3567					copyOfRefs.RemoveData("refs", index);
3568				} else {
3569					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3570					index++;
3571				}
3572			}
3573
3574			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3575			team_id team;
3576			error = be_roster->Launch(&app, refsToPass, &team);
3577			if (error == B_ALREADY_RUNNING)
3578				// app already running, not really an error
3579				error = B_OK;
3580			if (error == B_OK || mimesetIt != 0)
3581				break;
3582
3583			SniffIfGeneric(&copyOfRefs);
3584		}
3585	}
3586
3587	if (error != B_OK && alertString.Length() == 0) {
3588		BString loaderErrorString;
3589		bool openedDocuments = true;
3590
3591		if (!refsToPass) {
3592			// we just double clicked the app itself, do not offer to
3593			// find a handling app
3594			openWithOK = false;
3595			openedDocuments = false;
3596		}
3597		if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) {
3598			// We know it's an executable, but something unsupported
3599			alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported "
3600				"executable."));
3601			alertString.ReplaceFirst("%name", app.name);
3602		} else if (error == B_LEGACY_EXECUTABLE && !refsToPass) {
3603			// For the moment, this marks an old R3 binary, we may want to
3604			// extend it to gcc2 binaries someday post R1
3605			alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. "
3606				"Please obtain an updated version or recompile "
3607				"the application."));
3608			alertString.ReplaceFirst("%name", app.name);
3609		} else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3610			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3611				"The file is mistakenly marked as executable. "));
3612			alertString.ReplaceFirst("%name", app.name);
3613
3614			if (!openWithOK) {
3615				// offer the possibility to change the permissions
3616
3617				alertString << B_TRANSLATE("\nShould this be fixed?");
3618				BAlert* alert = new BAlert("", alertString.String(),
3619					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3620					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3621				alert->SetShortcut(0, B_ESCAPE);
3622				if (alert->Go() == 1) {
3623					BEntry entry(&documentRef);
3624					mode_t permissions;
3625
3626					error = entry.GetPermissions(&permissions);
3627					if (error == B_OK) {
3628						error = entry.SetPermissions(permissions
3629							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3630					}
3631					if (error == B_OK) {
3632						// we updated the permissions, so let's try again
3633						_TrackerLaunchDocuments(NULL, refs, false);
3634						return;
3635					} else {
3636						alertString.SetTo(B_TRANSLATE("Could not update "
3637							"permissions of file \"%name\". %error"));
3638						alertString.ReplaceFirst("%name", app.name);
3639						alertString.ReplaceFirst("%error", strerror(error));
3640					}
3641				} else
3642					return;
3643			}
3644
3645			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3646		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3647			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3648				"because application \"%app\" is in the Trash. "));
3649			alertString.ReplaceFirst("%document", documentRef.name);
3650			alertString.ReplaceFirst("%app", app.name);
3651			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3652		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3653			alertString.SetTo(
3654				B_TRANSLATE("Could not open \"%name\" (%error). "));
3655			alertString.ReplaceFirst("%name", documentRef.name);
3656			alertString.ReplaceFirst("%error", strerror(error));
3657			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3658		} else if (error == B_MISSING_SYMBOL
3659			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3660			if (openedDocuments) {
3661				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3662					"with application \"%app\" (Missing symbol: %symbol). "
3663					"\n"));
3664				alertString.ReplaceFirst("%document", documentRef.name);
3665				alertString.ReplaceFirst("%app", app.name);
3666				alertString.ReplaceFirst("%symbol",
3667					loaderErrorString.String());
3668			} else {
3669				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3670					"(Missing symbol: %symbol). \n"));
3671				alertString.ReplaceFirst("%document", documentRef.name);
3672				alertString.ReplaceFirst("%symbol",
3673					loaderErrorString.String());
3674			}
3675			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3676		} else if (error == B_MISSING_LIBRARY
3677			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3678			if (openedDocuments) {
3679				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3680					"with application \"%app\" (Missing libraries: %library). "
3681					"\n"));
3682				alertString.ReplaceFirst("%document", documentRef.name);
3683				alertString.ReplaceFirst("%app", app.name);
3684				alertString.ReplaceFirst("%library",
3685					loaderErrorString.String());
3686			} else {
3687				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3688					"(Missing libraries: %library). \n"));
3689				alertString.ReplaceFirst("%document", documentRef.name);
3690				alertString.ReplaceFirst("%library",
3691					loaderErrorString.String());
3692			}
3693			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3694		} else {
3695			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3696				"application \"%app\" (%error). "));
3697				alertString.ReplaceFirst("%document", documentRef.name);
3698				alertString.ReplaceFirst("%app", app.name);
3699				alertString.ReplaceFirst("%error", strerror(error));
3700			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3701		}
3702	}
3703
3704	if (error != B_OK) {
3705		if (openWithOK) {
3706			ASSERT(alternative);
3707			alertString << alternative;
3708			BAlert* alert = new BAlert("", alertString.String(),
3709				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3710				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3711			alert->SetShortcut(0, B_ESCAPE);
3712			if (alert->Go() == 1)
3713				error = TrackerOpenWith(refs);
3714		} else {
3715			BAlert* alert = new BAlert("", alertString.String(),
3716				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3717				B_WARNING_ALERT);
3718			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3719			alert->Go();
3720		}
3721	}
3722}
3723
3724// the following three calls don't return any reasonable error codes,
3725// should fix that, making them void
3726
3727status_t
3728TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async,
3729	bool openWithOK)
3730{
3731	if (!async)
3732		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3733	else {
3734		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3735			openWithOK);
3736	}
3737
3738	return B_OK;
3739}
3740
3741status_t
3742TrackerLaunch(const entry_ref* appRef, bool async)
3743{
3744	if (!async)
3745		_TrackerLaunchAppWithDocuments(appRef, NULL, false);
3746	else
3747		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3748
3749	return B_OK;
3750}
3751
3752status_t
3753TrackerLaunch(const BMessage* refs, bool async, bool openWithOK)
3754{
3755	if (!async)
3756		_TrackerLaunchDocuments(NULL, refs, openWithOK);
3757	else
3758		AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK);
3759
3760	return B_OK;
3761}
3762
3763
3764// external launch calls; need to be robust, work if Tracker is not running
3765
3766
3767#if !B_BEOS_VERSION_DANO
3768_IMPEXP_TRACKER
3769#endif
3770status_t
3771FSLaunchItem(const entry_ref* application, const BMessage* refsReceived,
3772	bool async, bool openWithOK)
3773{
3774	return TrackerLaunch(application, refsReceived, async, openWithOK);
3775}
3776
3777
3778#if !B_BEOS_VERSION_DANO
3779_IMPEXP_TRACKER
3780#endif
3781status_t
3782FSOpenWith(BMessage* listOfRefs)
3783{
3784	status_t result = B_ERROR;
3785	listOfRefs->what = B_REFS_RECEIVED;
3786
3787	if (dynamic_cast<TTracker*>(be_app) != NULL)
3788		result = TrackerOpenWith(listOfRefs);
3789	else
3790		ASSERT(!"not yet implemented");
3791
3792	return result;
3793}
3794
3795
3796// legacy calls, need for compatibility
3797
3798
3799void
3800FSOpenWithDocuments(const entry_ref* executable, BMessage* documents)
3801{
3802	TrackerLaunch(executable, documents, true);
3803	delete documents;
3804}
3805
3806
3807status_t
3808FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs)
3809{
3810	BMessage temp(B_REFS_RECEIVED);
3811	if (listOfRefs == NULL) {
3812		ASSERT(ref != NULL);
3813		temp.AddRef("refs", ref);
3814		listOfRefs = &temp;
3815	}
3816	FSOpenWith(listOfRefs);
3817
3818	return B_OK;
3819}
3820
3821
3822status_t
3823FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async)
3824{
3825	if (refs != NULL)
3826		refs->what = B_REFS_RECEIVED;
3827
3828	status_t result = TrackerLaunch(appRef, refs, async, true);
3829	delete refs;
3830
3831	return result;
3832}
3833
3834
3835void
3836FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace)
3837{
3838	FSLaunchItem(appRef, refs, workspace, true);
3839}
3840
3841
3842// Get the original path of an entry in the trash
3843status_t
3844FSGetOriginalPath(BEntry* entry, BPath* result)
3845{
3846	status_t err;
3847	entry_ref ref;
3848	err = entry->GetRef(&ref);
3849	if (err != B_OK)
3850		return err;
3851
3852	// Only call the routine for entries in the trash
3853	if (!FSInTrashDir(&ref))
3854		return B_ERROR;
3855
3856	BNode node(entry);
3857	BString originalPath;
3858	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3859		// We're in luck, the entry has the original path in an attribute
3860		err = result->SetTo(originalPath.String());
3861		return err;
3862	}
3863
3864	// Iterate the parent directories to find one with
3865	// the original path attribute
3866	BEntry parent(*entry);
3867	err = parent.InitCheck();
3868	if (err != B_OK)
3869		return err;
3870
3871	// walk up the directory structure until we find a node
3872	// with original path attribute
3873	do {
3874		// move to the parent of this node
3875		err = parent.GetParent(&parent);
3876		if (err != B_OK)
3877			return err;
3878
3879		// return if we are at the root of the trash
3880		if (FSIsTrashDir(&parent))
3881			return B_ENTRY_NOT_FOUND;
3882
3883		// get the parent as a node
3884		err = node.SetTo(&parent);
3885		if (err != B_OK)
3886			return err;
3887	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3888
3889	// Found the attribute, figure out there this file
3890	// used to live, based on the successfully-read attribute
3891	err = result->SetTo(originalPath.String());
3892	if (err != B_OK)
3893		return err;
3894
3895	BPath path, pathParent;
3896	err = parent.GetPath(&pathParent);
3897	if (err != B_OK)
3898		return err;
3899
3900	err = entry->GetPath(&path);
3901	if (err != B_OK)
3902		return err;
3903
3904	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3905		// compute the new path by appending the offset of
3906		// the item we are locating, to the original path
3907		// of the parent
3908
3909	return B_OK;
3910}
3911
3912
3913directory_which
3914WellKnowEntryList::Match(const node_ref* node)
3915{
3916	const WellKnownEntry* result = MatchEntry(node);
3917	if (result != NULL)
3918		return result->which;
3919
3920	return (directory_which)-1;
3921}
3922
3923
3924const WellKnowEntryList::WellKnownEntry*
3925WellKnowEntryList::MatchEntry(const node_ref* node)
3926{
3927	if (self == NULL)
3928		self = new WellKnowEntryList();
3929
3930	return self->MatchEntryCommon(node);
3931}
3932
3933
3934const WellKnowEntryList::WellKnownEntry*
3935WellKnowEntryList::MatchEntryCommon(const node_ref* node)
3936{
3937	uint32 count = entries.size();
3938	for (uint32 index = 0; index < count; index++) {
3939		if (*node == entries[index].node)
3940			return &entries[index];
3941	}
3942
3943	return NULL;
3944}
3945
3946
3947void
3948WellKnowEntryList::Quit()
3949{
3950	delete self;
3951	self = NULL;
3952}
3953
3954
3955void
3956WellKnowEntryList::AddOne(directory_which which, const char* name)
3957{
3958	BPath path;
3959	if (find_directory(which, &path, true) != B_OK)
3960		return;
3961
3962	BEntry entry(path.Path(), true);
3963	node_ref node;
3964	if (entry.GetNodeRef(&node) != B_OK)
3965		return;
3966
3967	entries.push_back(WellKnownEntry(&node, which, name));
3968}
3969
3970
3971void
3972WellKnowEntryList::AddOne(directory_which which, directory_which base,
3973	const char* extra, const char* name)
3974{
3975	BPath path;
3976	if (find_directory(base, &path, true) != B_OK)
3977		return;
3978
3979	path.Append(extra);
3980	BEntry entry(path.Path(), true);
3981	node_ref node;
3982	if (entry.GetNodeRef(&node) != B_OK)
3983		return;
3984
3985	entries.push_back(WellKnownEntry(&node, which, name));
3986}
3987
3988
3989void
3990WellKnowEntryList::AddOne(directory_which which, const char* path,
3991	const char* name)
3992{
3993	BEntry entry(path, true);
3994	node_ref node;
3995	if (entry.GetNodeRef(&node) != B_OK)
3996		return;
3997
3998	entries.push_back(WellKnownEntry(&node, which, name));
3999}
4000
4001
4002WellKnowEntryList::WellKnowEntryList()
4003{
4004	AddOne(B_SYSTEM_DIRECTORY, "system");
4005	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
4006	AddOne(B_USER_DIRECTORY, "home");
4007
4008	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
4009	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
4010
4011	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
4012	AddOne(B_APPS_DIRECTORY, "apps");
4013	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
4014		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
4015
4016	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
4017	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
4018	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
4019		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
4020
4021	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
4022		"mail");
4023
4024	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
4025		"queries", "queries");
4026
4027	AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop");
4028	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
4029		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
4030
4031	AddOne(B_USER_CONFIG_DIRECTORY, "config");
4032
4033	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
4034		"people", "people");
4035
4036	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
4037		"downloads", "downloads");
4038}
4039
4040WellKnowEntryList* WellKnowEntryList::self = NULL;
4041
4042} // namespace BPrivate
4043