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