xref: /haiku/src/kits/tracker/FSUtils.cpp (revision b289aaf66bbf6e173aa90fa194fc256965f1b34d)
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 
2360 	BVolume volume(dev);
2361 	status_t result = volume.InitCheck();
2362 	if (result != B_OK)
2363 		return result;
2364 
2365 	BPath path;
2366 	result = find_directory(B_TRASH_DIRECTORY, &path, true, &volume);
2367 	if (result != B_OK)
2368 		return result;
2369 
2370 	result = trashDir->SetTo(path.Path());
2371 	if (result != B_OK)
2372 		return result;
2373 
2374 	return B_OK;
2375 }
2376 
2377 
2378 #if __GNUC__ && __GNUC__ < 3
2379 // obsolete version of FSGetDeskDir retained for bin compat with
2380 // BeIDE and a few other apps that apparently use it
2381 status_t
2382 FSGetDeskDir(BDirectory *deskDir, dev_t)
2383 {
2384 	// since we no longer keep a desktop directory on any volume other
2385 	// than /boot, redirect to FSGetDeskDir ignoring the volume argument
2386 	return FSGetDeskDir(deskDir);
2387 }
2388 #endif
2389 
2390 
2391 status_t
2392 FSGetDeskDir(BDirectory *deskDir)
2393 {
2394 	BPath path;
2395 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2396 	if (result != B_OK)
2397 		return result;
2398 
2399 	result = deskDir->SetTo(path.Path());
2400 	if (result != B_OK)
2401 		return result;
2402 
2403 	size_t size;
2404 	const void* data = GetTrackerResources()->
2405 		LoadResource('ICON', R_DeskIcon, &size);
2406 	if (data != NULL)
2407 		deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2408 
2409 	data = GetTrackerResources()->
2410 		LoadResource('MICN', R_DeskIcon, &size);
2411 	if (data != NULL)
2412 		deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2413 
2414 #ifdef __HAIKU__
2415 	data = GetTrackerResources()->
2416 		LoadResource(B_VECTOR_ICON_TYPE, R_DeskIcon, &size);
2417 	if (data != NULL)
2418 		deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2419 #endif
2420 
2421 	return B_OK;
2422 }
2423 
2424 
2425 status_t
2426 FSGetBootDeskDir(BDirectory *deskDir)
2427 {
2428 	BVolume	bootVol;
2429 	BVolumeRoster().GetBootVolume(&bootVol);
2430 	BPath path;
2431 
2432 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2433 		&bootVol);
2434 	if (result != B_OK)
2435 		return result;
2436 
2437 	return deskDir->SetTo(path.Path());
2438 }
2439 
2440 
2441 static bool
2442 FSIsDirFlavor(const BEntry *entry, directory_which directoryType)
2443 {
2444 	StatStruct dir_stat;
2445 	StatStruct entry_stat;
2446 	BVolume volume;
2447 	BPath path;
2448 
2449 	if (entry->GetStat(&entry_stat) != B_OK)
2450 		return false;
2451 
2452 	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2453 		return false;
2454 
2455 	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2456 		return false;
2457 
2458 	stat(path.Path(), &dir_stat);
2459 
2460 	return dir_stat.st_ino == entry_stat.st_ino
2461 		&& dir_stat.st_dev == entry_stat.st_dev;
2462 }
2463 
2464 
2465 bool
2466 FSIsPrintersDir(const BEntry *entry)
2467 {
2468 	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2469 }
2470 
2471 
2472 bool
2473 FSIsTrashDir(const BEntry *entry)
2474 {
2475 	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2476 }
2477 
2478 
2479 bool
2480 FSIsDeskDir(const BEntry *entry)
2481 {
2482 	BPath path;
2483 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2484 	if (result != B_OK)
2485 		return false;
2486 
2487 	BEntry entryToCompare(path.Path());
2488 	return entryToCompare == *entry;
2489 }
2490 
2491 
2492 bool
2493 FSIsHomeDir(const BEntry *entry)
2494 {
2495 	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2496 }
2497 
2498 
2499 bool
2500 DirectoryMatchesOrContains(const BEntry *entry, directory_which which)
2501 {
2502 	BPath path;
2503 	if (find_directory(which, &path, false, NULL) != B_OK)
2504 		return false;
2505 
2506 	BEntry dirEntry(path.Path());
2507 	if (dirEntry.InitCheck() != B_OK)
2508 		return false;
2509 
2510 	if (dirEntry == *entry)
2511 		// root level match
2512 		return true;
2513 
2514 	BDirectory dir(&dirEntry);
2515 	return dir.Contains(entry);
2516 }
2517 
2518 
2519 bool
2520 DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath,
2521 	directory_which which)
2522 {
2523 	BPath path;
2524 	if (find_directory(which, &path, false, NULL) != B_OK)
2525 		return false;
2526 
2527 	path.Append(additionalPath);
2528 	BEntry dirEntry(path.Path());
2529 	if (dirEntry.InitCheck() != B_OK)
2530 		return false;
2531 
2532 	if (dirEntry == *entry)
2533 		// root level match
2534 		return true;
2535 
2536 	BDirectory dir(&dirEntry);
2537 	return dir.Contains(entry);
2538 }
2539 
2540 
2541 bool
2542 DirectoryMatches(const BEntry *entry, directory_which which)
2543 {
2544 	BPath path;
2545 	if (find_directory(which, &path, false, NULL) != B_OK)
2546 		return false;
2547 
2548 	BEntry dirEntry(path.Path());
2549 	if (dirEntry.InitCheck() != B_OK)
2550 		return false;
2551 
2552 	return dirEntry == *entry;
2553 }
2554 
2555 
2556 bool
2557 DirectoryMatches(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 	return dirEntry == *entry;
2570 }
2571 
2572 
2573 extern status_t
2574 FSFindTrackerSettingsDir(BPath *path, bool autoCreate)
2575 {
2576 	status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path,
2577 		autoCreate);
2578 	if (result != B_OK)
2579 		return result;
2580 
2581 	path->Append("Tracker");
2582 
2583 	return mkdir(path->Path(), 0777) ? B_OK : errno;
2584 }
2585 
2586 
2587 bool
2588 FSInTrashDir(const entry_ref *ref)
2589 {
2590 	BEntry entry(ref);
2591 	if (entry.InitCheck() != B_OK)
2592 		return false;
2593 
2594 	BDirectory trashDir;
2595 	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2596 		return false;
2597 
2598 	return trashDir.Contains(&entry);
2599 }
2600 
2601 
2602 void
2603 FSEmptyTrash()
2604 {
2605 	if (find_thread("_tracker_empty_trash_") != B_OK) {
2606 		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2607 			B_NORMAL_PRIORITY, NULL));
2608 	}
2609 }
2610 
2611 
2612 status_t
2613 empty_trash(void *)
2614 {
2615 	// empty trash on all mounted volumes
2616 	status_t err = B_OK;
2617 
2618 	TrackerCopyLoopControl loopControl(kTrashState);
2619 
2620 	// calculate the sum total of all items on all volumes in trash
2621 	BObjectList<entry_ref> srcList;
2622 	int32 totalCount = 0;
2623 	off_t totalSize = 0;
2624 
2625 	BVolumeRoster volumeRoster;
2626 	BVolume volume;
2627 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2628 		if (volume.IsReadOnly() || !volume.IsPersistent())
2629 			continue;
2630 
2631 		BDirectory trashDirectory;
2632 		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2633 			continue;
2634 
2635 		BEntry entry;
2636 		trashDirectory.GetEntry(&entry);
2637 
2638 		entry_ref ref;
2639 		entry.GetRef(&ref);
2640 		srcList.AddItem(&ref);
2641 		err = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
2642 			&totalCount, &totalSize);
2643 		if (err != B_OK)
2644 			break;
2645 
2646 		srcList.MakeEmpty();
2647 
2648 		// don't count trash directory itself
2649 		totalCount--;
2650 	}
2651 
2652 	if (err == B_OK) {
2653 		loopControl.Init(totalCount, totalCount);
2654 
2655 		volumeRoster.Rewind();
2656 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2657 			if (volume.IsReadOnly() || !volume.IsPersistent())
2658 				continue;
2659 
2660 			BDirectory trashDirectory;
2661 			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2662 				continue;
2663 
2664 			BEntry entry;
2665 			trashDirectory.GetEntry(&entry);
2666 			err = FSDeleteFolder(&entry, &loopControl, true, false);
2667 		}
2668 	}
2669 
2670 	if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) {
2671 		(new BAlert("", "Error emptying Trash!", "OK", NULL, NULL,
2672 			B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2673 	}
2674 
2675 	return B_OK;
2676 }
2677 
2678 
2679 status_t
2680 _DeleteTask(BObjectList<entry_ref> *list, bool confirm)
2681 {
2682 	if (confirm) {
2683 		bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
2684 
2685 		if (!dontMoveToTrash) {
2686 			BAlert *alert = new BAlert("", kDeleteConfirmationStr,
2687 				"Cancel", "Move to Trash", "Delete", B_WIDTH_AS_USUAL,
2688 				B_OFFSET_SPACING, B_WARNING_ALERT);
2689 
2690 			alert->SetShortcut(0, B_ESCAPE);
2691 			alert->SetShortcut(1, 'm');
2692 			alert->SetShortcut(2, 'd');
2693 
2694 			switch (alert->Go()) {
2695 				case 0:
2696 					delete list;
2697 					return B_OK;
2698 				case 1:
2699 					FSMoveToTrash(list, NULL, false);
2700 					return B_OK;
2701 			}
2702 		} else {
2703 			BAlert *alert = new BAlert("", kDeleteConfirmationStr,
2704 				"Cancel", "Delete", NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2705 				B_WARNING_ALERT);
2706 
2707 			alert->SetShortcut(0, B_ESCAPE);
2708 			alert->SetShortcut(1, 'd');
2709 
2710 			if (!alert->Go()) {
2711 				delete list;
2712 				return B_OK;
2713 			}
2714 		}
2715 	}
2716 
2717 	TrackerCopyLoopControl loopControl(kDeleteState);
2718 
2719 	// calculate the sum total of all items on all volumes in trash
2720 	int32 totalItems = 0;
2721 	int64 totalSize = 0;
2722 
2723 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2724 		&totalSize);
2725 	if (err == B_OK) {
2726 		loopControl.Init(totalItems, totalItems);
2727 
2728 		int32 count = list->CountItems();
2729 		for (int32 index = 0; index < count; index++) {
2730 			entry_ref ref(*list->ItemAt(index));
2731 			BEntry entry(&ref);
2732 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2733 			if (entry.IsDirectory())
2734 				err = FSDeleteFolder(&entry, &loopControl, true, true, true);
2735 			else
2736 				err = entry.Remove();
2737 		}
2738 
2739 		if (err != kTrashCanceled && err != kUserCanceled && err != B_OK)
2740 			(new BAlert("", "Error deleting items", "OK", NULL, NULL,
2741 				B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2742 	}
2743 
2744 	delete list;
2745 
2746 	return B_OK;
2747 }
2748 
2749 status_t
2750 FSRecursiveCreateFolder(BPath path)
2751 {
2752 	BEntry entry(path.Path());
2753 	if (entry.InitCheck() != B_OK) {
2754 		BPath parentPath;
2755 		status_t err = path.GetParent(&parentPath);
2756 		if (err != B_OK)
2757 			return err;
2758 
2759 		err = FSRecursiveCreateFolder(parentPath);
2760 		if (err != B_OK)
2761 			return err;
2762 	}
2763 
2764 	entry.SetTo(path.Path());
2765 	if (entry.Exists())
2766 		return B_FILE_EXISTS;
2767 	else {
2768 		char name[B_FILE_NAME_LENGTH];
2769 		BDirectory parent;
2770 
2771 		entry.GetParent(&parent);
2772 		entry.GetName(name);
2773 		parent.CreateDirectory(name, NULL);
2774 	}
2775 
2776 	return B_OK;
2777 }
2778 
2779 status_t
2780 _RestoreTask(BObjectList<entry_ref> *list)
2781 {
2782 	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
2783 
2784 	// calculate the sum total of all items that will be restored
2785 	int32 totalItems = 0;
2786 	int64 totalSize = 0;
2787 
2788 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2789 		&totalSize);
2790 	if (err == B_OK) {
2791 		loopControl.Init(totalItems, totalItems);
2792 
2793 		int32 count = list->CountItems();
2794 		for (int32 index = 0; index < count; index++) {
2795 			entry_ref ref(*list->ItemAt(index));
2796 			BEntry entry(&ref);
2797 			BPath originalPath;
2798 
2799 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2800 
2801 			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
2802 				continue;
2803 
2804 			BEntry originalEntry(originalPath.Path());
2805 			BPath parentPath;
2806 			err = originalPath.GetParent(&parentPath);
2807 			if (err != B_OK)
2808 				continue;
2809 			BEntry parentEntry(parentPath.Path());
2810 
2811 			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
2812 				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
2813 					originalEntry.SetTo(originalPath.Path());
2814 					if (entry.InitCheck() != B_OK)
2815 						continue;
2816 				}
2817 			}
2818 
2819 			if (!originalEntry.Exists()) {
2820 				BDirectory dir(parentPath.Path());
2821 				if (dir.InitCheck() == B_OK) {
2822 					char leafName[B_FILE_NAME_LENGTH];
2823 					originalEntry.GetName(leafName);
2824 					if (entry.MoveTo(&dir, leafName) == B_OK) {
2825 						BNode node(&entry);
2826 						if (node.InitCheck() == B_OK)
2827 							node.RemoveAttr(kAttrOriginalPath);
2828 					}
2829 				}
2830 			}
2831 
2832 			err = loopControl.CheckUserCanceled();
2833 			if (err != B_OK)
2834 				break;
2835 		}
2836 	}
2837 
2838 	delete list;
2839 
2840 	return err;
2841 }
2842 
2843 void
2844 FSCreateTrashDirs()
2845 {
2846 	BVolume volume;
2847 	BVolumeRoster roster;
2848 
2849 	roster.Rewind();
2850 	while (roster.GetNextVolume(&volume) == B_OK) {
2851 		if (volume.IsReadOnly() || !volume.IsPersistent())
2852 			continue;
2853 
2854 		BPath path;
2855 		find_directory(B_TRASH_DIRECTORY, &path, true, &volume);
2856 
2857 		BDirectory trashDir;
2858 		if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
2859 			// make trash invisible
2860 			StatStruct sbuf;
2861 			trashDir.GetStat(&sbuf);
2862 
2863 			PoseInfo poseInfo;
2864 			poseInfo.fInvisible = true;
2865 			poseInfo.fInitedDirectory = sbuf.st_ino;
2866 			trashDir.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2867 				sizeof(PoseInfo));
2868 
2869 			size_t size;
2870 			const void* data = GetTrackerResources()->
2871 				LoadResource('ICON', R_TrashIcon, &size);
2872 			if (data != NULL) {
2873 				trashDir.WriteAttr(kAttrLargeIcon, 'ICON', 0,
2874 					data, size);
2875 			}
2876 			data = GetTrackerResources()->
2877 				LoadResource('MICN', R_TrashIcon, &size);
2878 			if (data != NULL) {
2879 				trashDir.WriteAttr(kAttrMiniIcon, 'MICN', 0,
2880 					data, size);
2881 			}
2882 #ifdef __HAIKU
2883 			data = GetTrackerResources()->
2884 				LoadResource(B_VECTOR_ICON_TYPE, R_TrashIcon, &size);
2885 			if (data != NULL) {
2886 				trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0,
2887 					data, size);
2888 			}
2889 #endif
2890 		}
2891 	}
2892 }
2893 
2894 
2895 status_t
2896 FSCreateNewFolder(const entry_ref *ref)
2897 {
2898 	node_ref node;
2899 	node.device = ref->device;
2900 	node.node = ref->directory;
2901 
2902 	BDirectory dir(&node);
2903 	status_t result = dir.InitCheck();
2904 	if (result != B_OK)
2905 		return result;
2906 
2907 	// ToDo: is that really necessary here?
2908 	BString name(ref->name);
2909 	FSMakeOriginalName(name, &dir, "-");
2910 
2911 	BDirectory newDir;
2912 	result = dir.CreateDirectory(name.String(), &newDir);
2913 	if (result != B_OK)
2914 		return result;
2915 
2916 	BNodeInfo nodeInfo(&newDir);
2917 	nodeInfo.SetType(B_DIR_MIMETYPE);
2918 
2919 	return result;
2920 }
2921 
2922 
2923 status_t
2924 FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef,
2925 	node_ref *newNode)
2926 {
2927 	BDirectory dir(dirNode);
2928 	status_t result = dir.InitCheck();
2929 	if (result == B_OK) {
2930 		char name[B_FILE_NAME_LENGTH];
2931 		strcpy(name, "New folder");
2932 
2933 		int32 fnum = 1;
2934 		while (dir.Contains(name)) {
2935 			// if base name already exists then add a number
2936 			// ToDo:
2937 			// move this logic ot FSMakeOriginalName
2938 			if (++fnum > 9)
2939 				sprintf(name, "New folder%ld", fnum);
2940 			else
2941 				sprintf(name, "New folder %ld", fnum);
2942 		}
2943 
2944 		BDirectory newDir;
2945 		result = dir.CreateDirectory(name, &newDir);
2946 		if (result == B_OK) {
2947 			BEntry entry;
2948 			newDir.GetEntry(&entry);
2949 			entry.GetRef(newRef);
2950 			entry.GetNodeRef(newNode);
2951 
2952 			BNodeInfo nodeInfo(&newDir);
2953 			nodeInfo.SetType(B_DIR_MIMETYPE);
2954 
2955 			// add undo item
2956 			NewFolderUndo undo(*newRef);
2957 			return B_OK;
2958 		}
2959 	}
2960 
2961 	BAlert *alert = new BAlert("", "Sorry, could not create a new folder.",
2962 		"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2963 	alert->SetShortcut(0, B_ESCAPE);
2964 	alert->Go();
2965 	return result;
2966 }
2967 
2968 
2969 ReadAttrResult
2970 ReadAttr(const BNode *node, const char *hostAttrName,
2971 	const char *foreignAttrName, type_code type, off_t offset, void *buffer,
2972 	size_t length, void (*swapFunc)(void *), bool isForeign)
2973 {
2974 	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
2975 			length) == (ssize_t)length) {
2976 		return kReadAttrNativeOK;
2977 	}
2978 
2979 	// PRINT(("trying %s\n", foreignAttrName));
2980 	// try the other endianness
2981 	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
2982 			!= (ssize_t)length) {
2983 		return kReadAttrFailed;
2984 	}
2985 
2986 	// PRINT(("got %s\n", foreignAttrName));
2987 	if (!swapFunc)
2988 		return kReadAttrForeignOK;
2989 
2990 	(swapFunc)(buffer);
2991 		// run the endian swapper
2992 
2993 	return kReadAttrForeignOK;
2994 }
2995 
2996 
2997 ReadAttrResult
2998 GetAttrInfo(const BNode *node, const char *hostAttrName,
2999 	const char *foreignAttrName, type_code *type, size_t *size)
3000 {
3001 	attr_info info;
3002 
3003 	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3004 		if (type)
3005 			*type = info.type;
3006 		if (size)
3007 			*size = (size_t)info.size;
3008 
3009 		return kReadAttrNativeOK;
3010 	}
3011 
3012 	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3013 		if (type)
3014 			*type = info.type;
3015 		if (size)
3016 			*size = (size_t)info.size;
3017 
3018 		return kReadAttrForeignOK;
3019 	}
3020 	return kReadAttrFailed;
3021 }
3022 
3023 // launching code
3024 
3025 static status_t
3026 TrackerOpenWith(const BMessage *refs)
3027 {
3028 	BMessage clone(*refs);
3029 	ASSERT(dynamic_cast<TTracker *>(be_app));
3030 	ASSERT(clone.what);
3031 	clone.AddInt32("launchUsingSelector", 0);
3032 	// runs the Open With window
3033 	be_app->PostMessage(&clone);
3034 
3035 	return B_OK;
3036 }
3037 
3038 
3039 static void
3040 AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on),
3041 	const entry_ref *appRef, const BMessage *refs, bool openWithOK)
3042 {
3043 	BMessage *task = new BMessage;
3044 	task->AddPointer("function", (void *)func);
3045 	task->AddMessage("refs", refs);
3046 	task->AddBool("openWithOK", openWithOK);
3047 	if (appRef != NULL)
3048 		task->AddRef("appRef", appRef);
3049 
3050 	extern BLooper *gLaunchLooper;
3051 	gLaunchLooper->PostMessage(task);
3052 }
3053 
3054 static bool
3055 SniffIfGeneric(const entry_ref *ref)
3056 {
3057 	BNode node(ref);
3058 	char type[B_MIME_TYPE_LENGTH];
3059 	BNodeInfo info(&node);
3060 	if (info.GetType(type) == B_OK
3061 		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3062 		// already has a type and it's not octet stream
3063 		return false;
3064 	}
3065 
3066 	BPath path(ref);
3067 	if (path.Path()) {
3068 		// force a mimeset
3069 		node.RemoveAttr(kAttrMIMEType);
3070 		update_mime_info(path.Path(), 0, 1, 1);
3071 	}
3072 
3073 	return true;
3074 }
3075 
3076 static void
3077 SniffIfGeneric(const BMessage *refs)
3078 {
3079 	entry_ref ref;
3080 	for (int32 index = 0; ; index++) {
3081 		if (refs->FindRef("refs", index, &ref) != B_OK)
3082 			break;
3083 		SniffIfGeneric(&ref);
3084 	}
3085 }
3086 
3087 static void
3088 _TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs,
3089 	bool openWithOK)
3090 {
3091 	team_id team;
3092 
3093 	status_t error = B_ERROR;
3094 	BString alertString;
3095 
3096 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3097 		error = be_roster->Launch(appRef, refs, &team);
3098 		if (error == B_ALREADY_RUNNING)
3099 			// app already running, not really an error
3100 			error = B_OK;
3101 
3102 		if (error == B_OK)
3103 			break;
3104 
3105 		if (mimesetIt > 0)
3106 			break;
3107 
3108 		// failed to open, try mimesetting the refs and launching again
3109 		SniffIfGeneric(refs);
3110 	}
3111 
3112 	if (error == B_OK) {
3113 		// close possible parent window, if specified
3114 		const node_ref *nodeToClose = 0;
3115 		int32 numBytes;
3116 		refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3117 			(const void **)&nodeToClose, &numBytes);
3118 		if (nodeToClose)
3119 			dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose);
3120 	} else {
3121 		alertString << "Could not open \"" << appRef->name << "\" ("
3122 			<< strerror(error) << "). ";
3123 		if (refs && openWithOK && error != B_SHUTTING_DOWN) {
3124 			alertString << kFindAlternativeStr;
3125 			BAlert *alert = new BAlert("", alertString.String(),
3126 				"Cancel", "Find", 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3127 			alert->SetShortcut(0, B_ESCAPE);
3128 			if (alert->Go() == 1)
3129 				error = TrackerOpenWith(refs);
3130 		} else {
3131 			BAlert *alert = new BAlert("", alertString.String(),
3132 				"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3133 			alert->SetShortcut(0, B_ESCAPE);
3134 			alert->Go();
3135 		}
3136 	}
3137 }
3138 
3139 extern "C" char** environ;
3140 
3141 extern "C" status_t _kern_load_image(const char * const *flatArgs,
3142 	size_t flatArgsSize, int32 argCount, int32 envCount, int32 priority,
3143 	uint32 flags, port_id errorPort, uint32 errorToken);
3144 extern "C" status_t __flatten_process_args(const char * const *args,
3145 	int32 argCount, const char * const *env, int32 envCount, char ***_flatArgs,
3146 	size_t *_flatSize);
3147 
3148 
3149 static status_t
3150 LoaderErrorDetails(const entry_ref *app, BString &details)
3151 {
3152 	BPath path;
3153 	BEntry appEntry(app, true);
3154 
3155 	status_t result = appEntry.GetPath(&path);
3156 	if (result != B_OK)
3157 		return result;
3158 
3159 	char *argv[2] = { const_cast<char *>(path.Path()), 0};
3160 
3161 	port_id errorPort = create_port(1, "Tracker loader error");
3162 
3163 	// count environment variables
3164 	uint32 envCount = 0;
3165 	while (environ[envCount] != NULL)
3166 		envCount++;
3167 
3168 	char** flatArgs = NULL;
3169 	size_t flatArgsSize;
3170 	result = __flatten_process_args((const char **)argv, 1,
3171 		environ, envCount, &flatArgs, &flatArgsSize);
3172 	if (result != B_OK)
3173 		return result;
3174 
3175 	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3176 		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3177 	if (result == B_OK) {
3178 		// we weren't supposed to be able to start the application...
3179 		return B_ERROR;
3180 	}
3181 
3182 	// read error message from port and construct details string
3183 
3184 	ssize_t bufferSize;
3185 
3186 	do {
3187 		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3188 	} while (bufferSize == B_INTERRUPTED);
3189 
3190 	if (bufferSize <= B_OK) {
3191 		delete_port(errorPort);
3192 		return bufferSize;
3193 	}
3194 
3195 	uint8 *buffer = (uint8 *)malloc(bufferSize);
3196 	if (buffer == NULL) {
3197 		delete_port(errorPort);
3198 		return B_NO_MEMORY;
3199 	}
3200 
3201 	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3202 		B_RELATIVE_TIMEOUT, 0);
3203 	delete_port(errorPort);
3204 
3205 	if (bufferSize < B_OK) {
3206 		free(buffer);
3207 		return bufferSize;
3208 	}
3209 
3210 	BMessage message;
3211 	result = message.Unflatten((const char *)buffer);
3212 	free(buffer);
3213 
3214 	if (result != B_OK)
3215 		return result;
3216 
3217 	int32 errorCode = B_ERROR;
3218 	result = message.FindInt32("error", &errorCode);
3219 	if (result != B_OK)
3220 		return result;
3221 
3222 	const char *detailName = NULL;
3223 	switch (errorCode) {
3224 		case B_MISSING_LIBRARY:
3225 			detailName = "missing library";
3226 			break;
3227 
3228 		case B_MISSING_SYMBOL:
3229 			detailName = "missing symbol";
3230 			break;
3231 	}
3232 
3233 	if (detailName == NULL)
3234 		return B_ERROR;
3235 
3236 	const char *detail;
3237 	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3238 			i++) {
3239 		if (i > 0)
3240 			details += ", ";
3241 		details += detail;
3242 	}
3243 
3244 	return B_OK;
3245 }
3246 
3247 
3248 static void
3249 _TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs,
3250 	bool openWithOK)
3251 {
3252 	BMessage copyOfRefs(*refs);
3253 
3254 	entry_ref documentRef;
3255 	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK)
3256 		// nothing to launch, we are done
3257 		return;
3258 
3259 	status_t error = B_ERROR;
3260 	entry_ref app;
3261 	BMessage *refsToPass = NULL;
3262 	BString alertString;
3263 	const char *alternative = 0;
3264 
3265 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3266 		alertString = "";
3267 		error = be_roster->FindApp(&documentRef, &app);
3268 
3269 		if (error != B_OK && mimesetIt == 0) {
3270 			SniffIfGeneric(&copyOfRefs);
3271 			continue;
3272 		}
3273 
3274 		if (error != B_OK) {
3275 			alertString << "Could not find an application to open \""
3276 				<< documentRef.name << "\" (" << strerror(error) << "). ";
3277 			if (openWithOK)
3278 				alternative = kFindApplicationStr;
3279 
3280 			break;
3281 		} else {
3282 			BEntry appEntry(&app, true);
3283 			for (int32 index = 0;;) {
3284 				// remove the app itself from the refs received so we don't try
3285 				// to open ourselves
3286 				entry_ref ref;
3287 				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3288 					break;
3289 
3290 				// deal with symlinks properly
3291 				BEntry documentEntry(&ref, true);
3292 				if (appEntry == documentEntry) {
3293 					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3294 					copyOfRefs.RemoveData("refs", index);
3295 				} else {
3296 					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3297 					index++;
3298 				}
3299 			}
3300 
3301 			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3302 			team_id team;
3303 			error = be_roster->Launch(&app, refsToPass, &team);
3304 			if (error == B_ALREADY_RUNNING)
3305 				// app already running, not really an error
3306 				error = B_OK;
3307 			if (error == B_OK || mimesetIt != 0)
3308 				break;
3309 
3310 			SniffIfGeneric(&copyOfRefs);
3311 		}
3312 	}
3313 
3314 	if (error != B_OK && alertString.Length() == 0) {
3315 		BString loaderErrorString;
3316 		bool openedDocuments = true;
3317 
3318 		if (!refsToPass) {
3319 			// we just double clicked the app itself, do not offer to
3320 			// find a handling app
3321 			openWithOK = false;
3322 			openedDocuments = false;
3323 		}
3324 
3325 		if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3326 			alertString << "Could not open \"" << app.name
3327 				<< "\". The file is mistakenly marked as executable. ";
3328 
3329 			if (!openWithOK) {
3330 				// offer the possibility to change the permissions
3331 
3332 				alertString << "\nShould this be fixed?";
3333 				BAlert *alert = new BAlert("", alertString.String(),
3334 					"Cancel", "Proceed", 0,	B_WIDTH_AS_USUAL,
3335 					B_WARNING_ALERT);
3336 				alert->SetShortcut(0, B_ESCAPE);
3337 				if (alert->Go() == 1) {
3338 					BEntry entry(&documentRef);
3339 					mode_t permissions;
3340 
3341 					error = entry.GetPermissions(&permissions);
3342 					if (error == B_OK) {
3343 						error = entry.SetPermissions(permissions
3344 							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3345 					}
3346 					if (error == B_OK) {
3347 						// we updated the permissions, so let's try again
3348 						_TrackerLaunchDocuments(NULL, refs, false);
3349 						return;
3350 					} else {
3351 						alertString = "Could not update permissions of "
3352 							"file \"";
3353 						alertString << app.name << "\". " << strerror(error);
3354 					}
3355 				} else
3356 					return;
3357 			}
3358 
3359 			alternative = kFindApplicationStr;
3360 		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3361 			alertString << "Could not open \"" << documentRef.name
3362 				<< "\" because application \"" << app.name << "\" is in the "
3363 					"Trash. ";
3364 			alternative = kFindAlternativeStr;
3365 		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3366 			alertString << "Could not open \"" << documentRef.name << "\" "
3367 				<< "(" << strerror(error) << "). ";
3368 			alternative = kFindAlternativeStr;
3369 		} else if (error == B_MISSING_SYMBOL
3370 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3371 			alertString << "Could not open \"" << documentRef.name << "\" ";
3372 			if (openedDocuments)
3373 				alertString << "with application \"" << app.name << "\" ";
3374 			alertString << "(Missing symbol: " << loaderErrorString << "). \n";
3375 			alternative = kFindAlternativeStr;
3376 		} else if (error == B_MISSING_LIBRARY
3377 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3378 			alertString << "Could not open \"" << documentRef.name << "\" ";
3379 			if (openedDocuments)
3380 				alertString << "with application \"" << app.name << "\" ";
3381 			alertString << "(Missing libraries: " << loaderErrorString
3382 				<< "). \n";
3383 			alternative = kFindAlternativeStr;
3384 		} else {
3385 			alertString << "Could not open \"" << documentRef.name
3386 				<< "\" with application \"" << app.name << "\" ("
3387 				<< strerror(error) << "). ";
3388 			alternative = kFindAlternativeStr;
3389 		}
3390 	}
3391 
3392 	if (error != B_OK) {
3393 		if (openWithOK) {
3394 			ASSERT(alternative);
3395 			alertString << alternative;
3396 			BAlert *alert = new BAlert("", alertString.String(),
3397 				"Cancel", "Find", 0, B_WIDTH_AS_USUAL,
3398 				B_WARNING_ALERT);
3399 			alert->SetShortcut(0, B_ESCAPE);
3400 			if (alert->Go() == 1)
3401 				error = TrackerOpenWith(refs);
3402 		} else {
3403 			BAlert *alert = new BAlert("", alertString.String(),
3404 				"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3405 			alert->SetShortcut(0, B_ESCAPE);
3406 			alert->Go();
3407 		}
3408 	}
3409 }
3410 
3411 // the following three calls don't return any reasonable error codes,
3412 // should fix that, making them void
3413 
3414 status_t
3415 TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async,
3416 	bool openWithOK)
3417 {
3418 	if (!async)
3419 		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3420 	else {
3421 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3422 			openWithOK);
3423 	}
3424 
3425 	return B_OK;
3426 }
3427 
3428 status_t
3429 TrackerLaunch(const entry_ref *appRef, bool async)
3430 {
3431 	if (!async)
3432 		_TrackerLaunchAppWithDocuments(appRef, 0, false);
3433 	else
3434 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3435 
3436 	return B_OK;
3437 }
3438 
3439 status_t
3440 TrackerLaunch(const BMessage *refs, bool async, bool openWithOK)
3441 {
3442 	if (!async)
3443 		_TrackerLaunchDocuments(0, refs, openWithOK);
3444 	else
3445 		AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK);
3446 
3447 	return B_OK;
3448 }
3449 
3450 status_t
3451 LaunchBrokenLink(const char *signature, const BMessage *refs)
3452 {
3453 	// This call is to support a hacky workaround for double-clicking
3454 	// broken refs for cifs
3455 	be_roster->Launch(signature, const_cast<BMessage *>(refs));
3456 	return B_OK;
3457 }
3458 
3459 // external launch calls; need to be robust, work if Tracker is not running
3460 
3461 #if !B_BEOS_VERSION_DANO
3462 _IMPEXP_TRACKER
3463 #endif
3464 status_t
3465 FSLaunchItem(const entry_ref *application, const BMessage *refsReceived,
3466 	bool async, bool openWithOK)
3467 {
3468 	return TrackerLaunch(application, refsReceived, async, openWithOK);
3469 }
3470 
3471 
3472 #if !B_BEOS_VERSION_DANO
3473 _IMPEXP_TRACKER
3474 #endif
3475 status_t
3476 FSOpenWith(BMessage *listOfRefs)
3477 {
3478 	status_t result = B_ERROR;
3479 	listOfRefs->what = B_REFS_RECEIVED;
3480 
3481 	if (dynamic_cast<TTracker *>(be_app))
3482 		result = TrackerOpenWith(listOfRefs);
3483 	else
3484 		ASSERT(!"not yet implemented");
3485 
3486 	return result;
3487 }
3488 
3489 // legacy calls, need for compatibility
3490 
3491 void
3492 FSOpenWithDocuments(const entry_ref *executable, BMessage *documents)
3493 {
3494 	TrackerLaunch(executable, documents, true);
3495 	delete documents;
3496 }
3497 
3498 status_t
3499 FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs)
3500 {
3501 	BMessage temp(B_REFS_RECEIVED);
3502 	if (!listOfRefs) {
3503 		ASSERT(ref);
3504 		temp.AddRef("refs", ref);
3505 		listOfRefs = &temp;
3506 	}
3507 	FSOpenWith(listOfRefs);
3508 	return B_OK;
3509 }
3510 
3511 status_t
3512 FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async)
3513 {
3514 	if (message)
3515 		message->what = B_REFS_RECEIVED;
3516 
3517 	status_t result = TrackerLaunch(ref, message, async, true);
3518 	delete message;
3519 	return result;
3520 }
3521 
3522 
3523 void
3524 FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace)
3525 {
3526 	FSLaunchItem(ref, message, workspace, true);
3527 }
3528 
3529 // Get the original path of an entry in the trash
3530 status_t
3531 FSGetOriginalPath(BEntry *entry, BPath *result)
3532 {
3533 	status_t err;
3534 	entry_ref ref;
3535 	err = entry->GetRef(&ref);
3536 	if (err != B_OK)
3537 		return err;
3538 
3539 	// Only call the routine for entries in the trash
3540 	if (!FSInTrashDir(&ref))
3541 		return B_ERROR;
3542 
3543 	BNode node(entry);
3544 	BString originalPath;
3545 	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3546 		// We're in luck, the entry has the original path in an attribute
3547 		err = result->SetTo(originalPath.String());
3548 		return err;
3549 	}
3550 
3551 	// Iterate the parent directories to find one with
3552 	// the original path attribute
3553 	BEntry parent(*entry);
3554 	err = parent.InitCheck();
3555 	if (err != B_OK)
3556 		return err;
3557 
3558 	// walk up the directory structure until we find a node
3559 	// with original path attribute
3560 	do {
3561 		// move to the parent of this node
3562 		err = parent.GetParent(&parent);
3563 		if (err != B_OK)
3564 			return err;
3565 
3566 		// return if we are at the root of the trash
3567 		if (FSIsTrashDir(&parent))
3568 			return B_ENTRY_NOT_FOUND;
3569 
3570 		// get the parent as a node
3571 		err = node.SetTo(&parent);
3572 		if (err != B_OK)
3573 			return err;
3574 	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3575 
3576 	// Found the attribute, figure out there this file
3577 	// used to live, based on the successfully-read attribute
3578 	err = result->SetTo(originalPath.String());
3579 	if (err != B_OK)
3580 		return err;
3581 
3582 	BPath path, pathParent;
3583 	err = parent.GetPath(&pathParent);
3584 	if (err != B_OK)
3585 		return err;
3586 	err = entry->GetPath(&path);
3587 	if (err != B_OK)
3588 		return err;
3589 	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3590 		// compute the new path by appending the offset of
3591 		// the item we are locating, to the original path
3592 		// of the parent
3593 	return B_OK;
3594 }
3595 
3596 directory_which
3597 WellKnowEntryList::Match(const node_ref *node)
3598 {
3599 	const WellKnownEntry *result = MatchEntry(node);
3600 	if (result)
3601 		return result->which;
3602 
3603 	return (directory_which)-1;
3604 }
3605 
3606 const WellKnowEntryList::WellKnownEntry *
3607 WellKnowEntryList::MatchEntry(const node_ref *node)
3608 {
3609 	if (!self)
3610 		self = new WellKnowEntryList();
3611 
3612 	return self->MatchEntryCommon(node);
3613 }
3614 
3615 const WellKnowEntryList::WellKnownEntry *
3616 WellKnowEntryList::MatchEntryCommon(const node_ref *node)
3617 {
3618 	uint32 count = entries.size();
3619 	for (uint32 index = 0; index < count; index++)
3620 		if (*node == entries[index].node)
3621 			return &entries[index];
3622 
3623 	return NULL;
3624 }
3625 
3626 
3627 void
3628 WellKnowEntryList::Quit()
3629 {
3630 	delete self;
3631 	self = NULL;
3632 }
3633 
3634 
3635 void
3636 WellKnowEntryList::AddOne(directory_which which, const char *name)
3637 {
3638 	BPath path;
3639 	if (find_directory(which, &path, true) != B_OK)
3640 		return;
3641 
3642 	BEntry entry(path.Path(), true);
3643 	node_ref node;
3644 	if (entry.GetNodeRef(&node) != B_OK)
3645 		return;
3646 
3647 	entries.push_back(WellKnownEntry(&node, which, name));
3648 }
3649 
3650 
3651 void
3652 WellKnowEntryList::AddOne(directory_which which, directory_which base,
3653 	const char *extra, const char *name)
3654 {
3655 	BPath path;
3656 	if (find_directory(base, &path, true) != B_OK)
3657 		return;
3658 
3659 	path.Append(extra);
3660 	BEntry entry(path.Path(), true);
3661 	node_ref node;
3662 	if (entry.GetNodeRef(&node) != B_OK)
3663 		return;
3664 
3665 	entries.push_back(WellKnownEntry(&node, which, name));
3666 }
3667 
3668 
3669 void
3670 WellKnowEntryList::AddOne(directory_which which, const char *path,
3671 	const char *name)
3672 {
3673 	BEntry entry(path, true);
3674 	node_ref node;
3675 	if (entry.GetNodeRef(&node) != B_OK)
3676 		return;
3677 
3678 	entries.push_back(WellKnownEntry(&node, which, name));
3679 }
3680 
3681 
3682 WellKnowEntryList::WellKnowEntryList()
3683 {
3684 #ifdef __HAIKU__
3685 	AddOne(B_SYSTEM_DIRECTORY, "system");
3686 #else
3687 	AddOne(B_BEOS_DIRECTORY, "beos");
3688 	AddOne(B_BEOS_SYSTEM_DIRECTORY, "system");
3689 #endif
3690 	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
3691 	AddOne(B_USER_DIRECTORY, "home");
3692 
3693 	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
3694 	AddOne(B_COMMON_FONTS_DIRECTORY, "fonts");
3695 	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
3696 
3697 	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
3698 	AddOne(B_APPS_DIRECTORY, "apps");
3699 	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
3700 		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
3701 
3702 	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
3703 	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
3704 	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
3705 		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
3706 
3707 	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
3708 		"mail");
3709 
3710 	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
3711 		"queries", "queries");
3712 
3713 
3714 
3715 	AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop");
3716 	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
3717 		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
3718 
3719 	AddOne(B_USER_CONFIG_DIRECTORY, "config");
3720 
3721 	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
3722 		"people", "people");
3723 
3724 	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
3725 		"downloads", "downloads");
3726 }
3727 
3728 WellKnowEntryList *WellKnowEntryList::self = NULL;
3729 
3730 } // namespace BPrivate
3731