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