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