xref: /haiku/src/kits/tracker/FSUtils.cpp (revision 1294543de9ac0eff000eaea1b18368c36435d08e)
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 "FSUtils"
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 %" B_PRIdDEV ", %" B_PRIdDEV "\n",
1497 					statbuf.st_dev, 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 
1992 	uint32 pos = sprintf(buffer,
1993 		B_TRANSLATE("\n\t(%Ld bytes, "), stat->st_size);
1994 	strftime(buffer + pos, length - pos,"%b %d %Y, %I:%M:%S %p)", &timeData);
1995 }
1996 
1997 
1998 status_t
1999 CheckName(uint32 moveMode, const BEntry *sourceEntry,
2000 	const BDirectory *destDir, bool multipleCollisions,
2001 	ConflictCheckResult &replaceAll)
2002 {
2003 	if (moveMode == kDuplicateSelection)
2004 		// when duplicating, we will never have a conflict
2005 		return B_OK;
2006 
2007 	// see if item already exists in destination dir
2008 	status_t err = B_OK;
2009 	char name[B_FILE_NAME_LENGTH];
2010 	sourceEntry->GetName(name);
2011 	bool sourceIsDirectory = sourceEntry->IsDirectory();
2012 
2013 	BDirectory srcDirectory;
2014 	if (sourceIsDirectory) {
2015 		srcDirectory.SetTo(sourceEntry);
2016 		BEntry destEntry;
2017 		destDir->GetEntry(&destEntry);
2018 
2019 		if (moveMode != kCreateLink
2020 			&& moveMode != kCreateRelativeLink
2021 			&& (srcDirectory == *destDir
2022 				|| srcDirectory.Contains(&destEntry))) {
2023 			(new BAlert("",
2024 				B_TRANSLATE("You can't move a folder into itself "
2025 				"or any of its own sub-folders."), B_TRANSLATE("OK"),
2026 				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2027 			return B_ERROR;
2028 		}
2029 	}
2030 
2031 	if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink
2032 		&& moveMode != kCreateRelativeLink) {
2033 		(new BAlert("",
2034 			B_TRANSLATE("You can't move or copy the trash."),
2035 			B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2036 		return B_ERROR;
2037 	}
2038 
2039 	BEntry entry;
2040 	if (destDir->FindEntry(name, &entry) != B_OK)
2041 		// no conflict, return
2042 		return B_OK;
2043 
2044 	if (moveMode == kCreateLink	|| moveMode == kCreateRelativeLink) {
2045 		// if we are creating link in the same directory, the conflict will
2046 		// be handled later by giving the link a unique name
2047 		sourceEntry->GetParent(&srcDirectory);
2048 
2049 		if (srcDirectory == *destDir)
2050 			return B_OK;
2051 	}
2052 
2053 	bool destIsDir = entry.IsDirectory();
2054 	// be sure not to replace the parent directory of the item being moved
2055 	if (destIsDir) {
2056 		BDirectory test_dir(&entry);
2057 		if (test_dir.Contains(sourceEntry)) {
2058 			(new BAlert("",
2059 				B_TRANSLATE("You can't replace a folder "
2060 				"with one of its sub-folders."),
2061 				B_TRANSLATE("OK"),
2062 				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2063 			return B_ERROR;
2064 		}
2065 	}
2066 
2067 	// Ensure user isn't trying to replace a file with folder or vice versa.
2068 	if (moveMode != kCreateLink
2069 		&& moveMode != kCreateRelativeLink
2070 		&& destIsDir != sourceIsDirectory) {
2071 			(new BAlert("", sourceIsDirectory
2072 				? B_TRANSLATE("You cannot replace a file with a folder or a "
2073 				"symbolic link.")
2074 				: B_TRANSLATE("You cannot replace a folder or a symbolic link "
2075 				"with a file."), B_TRANSLATE("OK"),	0, 0, B_WIDTH_AS_USUAL,
2076 				B_WARNING_ALERT))->Go();
2077 			return B_ERROR;
2078 		}
2079 
2080 	if (replaceAll != kReplaceAll) {
2081 		// prompt user to determine whether to replace or not
2082 
2083 		BString replaceMsg;
2084 
2085 		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2086 			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr));
2087 			replaceMsg.ReplaceFirst("%name", name);
2088 		} else if (sourceEntry->IsDirectory()) {
2089 			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr));
2090 			replaceMsg.ReplaceFirst("%name", name);
2091 			replaceMsg.ReplaceFirst("%verb",
2092 				moveMode == kMoveSelectionTo
2093 				? B_TRANSLATE("moving")
2094 				: B_TRANSLATE("copying"));
2095 		} else {
2096 			char sourceBuffer[96], destBuffer[96];
2097 			StatStruct statBuffer;
2098 
2099 			if (!sourceEntry->IsDirectory() && sourceEntry->GetStat(
2100 				&statBuffer) == B_OK) {
2101 				FileStatToString(&statBuffer, sourceBuffer, 96);
2102 			} else
2103 				sourceBuffer[0] = '\0';
2104 
2105 			if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
2106 				FileStatToString(&statBuffer, destBuffer, 96);
2107 			else
2108 				destBuffer[0] = '\0';
2109 
2110 			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr));
2111 			replaceMsg.ReplaceAll("%name", name);
2112 			replaceMsg.ReplaceFirst("%dest", destBuffer);
2113 			replaceMsg.ReplaceFirst("%src", sourceBuffer);
2114 			replaceMsg.ReplaceFirst("%movemode",
2115 				moveMode == kMoveSelectionTo
2116 				? B_TRANSLATE("moving")
2117 				: B_TRANSLATE("copying"));
2118 		}
2119 
2120 		// special case single collision (don't need Replace All shortcut)
2121 		BAlert *alert;
2122 		if (multipleCollisions || sourceIsDirectory) {
2123 			alert = new BAlert("", replaceMsg.String(),
2124 				B_TRANSLATE("Skip"), B_TRANSLATE("Replace all"));
2125 		} else {
2126 			alert = new BAlert("", replaceMsg.String(),
2127 				B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2128 			alert->SetShortcut(0, B_ESCAPE);
2129 		}
2130 		switch (alert->Go()) {
2131 			case 0:		// user selected "Cancel" or "Skip"
2132 				replaceAll = kCanceled;
2133 				return B_ERROR;
2134 
2135 			case 1:		// user selected "Replace" or "Replace All"
2136 				replaceAll = kReplaceAll;
2137 					// doesn't matter which since a single
2138 					// collision "Replace" is equivalent to a
2139 					// "Replace All"
2140 				break;
2141 		}
2142 	}
2143 
2144 	// delete destination item
2145 	if (!destIsDir)
2146 		err = entry.Remove();
2147 	else
2148 		return B_OK;
2149 
2150 	if (err != B_OK) {
2151 		BString error(B_TRANSLATE("There was a problem trying to replace \"%name\". The item might be open or busy."));
2152 		error.ReplaceFirst("%name", name);;
2153 		BAlert* alert = new BAlert("", error.String(),
2154 			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2155 		alert->SetShortcut(0, B_ESCAPE);
2156 		alert->Go();
2157 	}
2158 
2159 	return err;
2160 }
2161 
2162 
2163 status_t
2164 FSDeleteFolder(BEntry *dir_entry, CopyLoopControl *loopControl,
2165 	bool update_status, bool delete_top_dir, bool upateFileNameInStatus)
2166 {
2167 	entry_ref	ref;
2168 	BEntry		entry;
2169 	BDirectory	dir;
2170 	status_t	err;
2171 
2172 	dir.SetTo(dir_entry);
2173 	dir.Rewind();
2174 	// loop through everything in folder and delete it, skipping trouble files
2175 	for (;;) {
2176 		if (dir.GetNextEntry(&entry) != B_OK)
2177 			break;
2178 
2179 		entry.GetRef(&ref);
2180 
2181 		if (loopControl->CheckUserCanceled())
2182 			return kTrashCanceled;
2183 
2184 		if (entry.IsDirectory())
2185 			err = FSDeleteFolder(&entry, loopControl, update_status, true,
2186 				upateFileNameInStatus);
2187 		else {
2188 			err = entry.Remove();
2189 			if (update_status) {
2190 				loopControl->UpdateStatus(upateFileNameInStatus ? ref.name
2191 					: "", ref, 1, true);
2192 			}
2193 		}
2194 
2195 		if (err == kTrashCanceled)
2196 			return kTrashCanceled;
2197 		else if (err == B_OK)
2198 			dir.Rewind();
2199 		else {
2200 			loopControl->FileError(B_TRANSLATE_NOCOLLECT(
2201 					kFileDeleteErrorString), ref.name, err, false);
2202 		}
2203 	}
2204 
2205 	if (loopControl->CheckUserCanceled())
2206 		return kTrashCanceled;
2207 
2208 	dir_entry->GetRef(&ref);
2209 
2210 	if (update_status && delete_top_dir)
2211 		loopControl->UpdateStatus(NULL, ref, 1);
2212 
2213 	if (delete_top_dir)
2214 		return dir_entry->Remove();
2215 	else
2216 		return B_OK;
2217 }
2218 
2219 
2220 void
2221 FSMakeOriginalName(BString &string, const BDirectory *destDir,
2222 	const char *suffix)
2223 {
2224 	if (!destDir->Contains(string.String()))
2225 		return;
2226 
2227 	FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
2228 		const_cast<BDirectory *>(destDir), suffix ? suffix : " copy");
2229 	string.UnlockBuffer();
2230 }
2231 
2232 
2233 void
2234 FSMakeOriginalName(char *name, BDirectory *destDir, const char *suffix)
2235 {
2236 	char		root[B_FILE_NAME_LENGTH];
2237 	char		copybase[B_FILE_NAME_LENGTH];
2238 	char		temp_name[B_FILE_NAME_LENGTH + 10];
2239 	int32		fnum;
2240 
2241 	// is this name already original?
2242 	if (!destDir->Contains(name))
2243 		return;
2244 
2245 	// Determine if we're copying a 'copy'. This algorithm isn't perfect.
2246 	// If you're copying a file whose REAL name ends with 'copy' then
2247 	// this method will return "<filename> 1", not "<filename> copy"
2248 
2249 	// However, it will correctly handle file that contain 'copy'
2250 	// elsewhere in their name.
2251 
2252 	bool copycopy = false;		// are we copying a copy?
2253 	int32 len = (int32)strlen(name);
2254 	char *p = name + len - 1;	// get pointer to end os name
2255 
2256 	// eat up optional numbers (if were copying "<filename> copy 34")
2257 	while ((p > name) && isdigit(*p))
2258 		p--;
2259 
2260 	// eat up optional spaces
2261 	while ((p > name) && isspace(*p))
2262 		p--;
2263 
2264 	// now look for the phrase " copy"
2265 	if (p > name) {
2266 		// p points to the last char of the word. For example, 'y' in 'copy'
2267 
2268 		if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
2269 			// we found 'copy' in the right place.
2270 			// so truncate after 'copy'
2271 			*(p + 1) = '\0';
2272 			copycopy = true;
2273 
2274 			// save the 'root' name of the file, for possible later use.
2275 			// that is copy everything but trailing " copy". Need to
2276 			// NULL terminate after copy
2277 			strncpy(root, name, (uint32)((p - name) - 4));
2278 			root[(p - name) - 4] = '\0';
2279 		}
2280 	}
2281 
2282 	if (!copycopy) {
2283 		/*
2284 		 The name can't be longer than B_FILE_NAME_LENGTH.
2285 		 The algoritm adds " copy XX" to the name. That's 8 characters.
2286 		 B_FILE_NAME_LENGTH already accounts for NULL termination so we
2287 		 don't need to save an extra char at the end.
2288 		*/
2289 		if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
2290 			// name is too long - truncate it!
2291 			name[B_FILE_NAME_LENGTH - 8] = '\0';
2292 		}
2293 
2294 		strcpy(root, name);		// save root name
2295 		strcat(name, suffix);
2296 	}
2297 
2298 	strcpy(copybase, name);
2299 
2300 	// if name already exists then add a number
2301 	fnum = 1;
2302 	strcpy(temp_name, name);
2303 	while (destDir->Contains(temp_name)) {
2304 		sprintf(temp_name, "%s %ld", copybase, ++fnum);
2305 
2306 		if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) {
2307 			/*
2308 			 The name has grown too long. Maybe we just went from
2309 			 "<filename> copy 9" to "<filename> copy 10" and that extra
2310 			 character was too much. The solution is to further
2311 			 truncate the 'root' name and continue.
2312 			 ??? should we reset fnum or not ???
2313 			*/
2314 			root[strlen(root) - 1] = '\0';
2315 			sprintf(temp_name, "%s%s %ld", root, suffix, fnum);
2316 		}
2317 	}
2318 
2319 	ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1)));
2320 	strcpy(name, temp_name);
2321 }
2322 
2323 
2324 status_t
2325 FSRecursiveCalcSize(BInfoWindow *window, CopyLoopControl* loopControl,
2326 	BDirectory *dir, off_t *running_size, int32 *fileCount, int32 *dirCount)
2327 {
2328 	dir->Rewind();
2329 	BEntry entry;
2330 	while (dir->GetNextEntry(&entry) == B_OK) {
2331 		// be sure window hasn't closed
2332 		if (window && window->StopCalc())
2333 			return B_OK;
2334 
2335 		if (loopControl->CheckUserCanceled())
2336 			return kUserCanceled;
2337 
2338 		StatStruct statbuf;
2339 		entry.GetStat(&statbuf);
2340 
2341 		if (S_ISDIR(statbuf.st_mode)) {
2342 			BDirectory subdir(&entry);
2343 			(*dirCount)++;
2344 			(*running_size) += 1024;
2345 			status_t result = FSRecursiveCalcSize(window, loopControl, &subdir,
2346 				running_size, fileCount, dirCount);
2347 			if (result != B_OK)
2348 				return result;
2349 		} else {
2350 			(*fileCount)++;
2351 			(*running_size) += statbuf.st_size + 1024;
2352 				// Add to compensate for attributes.
2353 		}
2354 	}
2355 	return B_OK;
2356 }
2357 
2358 
2359 status_t
2360 CalcItemsAndSize(CopyLoopControl* loopControl, BObjectList<entry_ref> *refList,
2361 	size_t blockSize, int32 *totalCount, off_t *totalSize)
2362 {
2363 	int32 fileCount = 0;
2364 	int32 dirCount = 0;
2365 
2366 	// check block size for sanity
2367 	if (blockSize < 0) {
2368 		// This would point at an error to retrieve the block size from
2369 		// the target volume. The code below cannot be used, it is only
2370 		// meant to get the block size when item operations happen on
2371 		// the source volume.
2372 		blockSize = 2048;
2373 	} else if (blockSize < 1024) {
2374 		blockSize = 1024;
2375 		if (entry_ref* ref = refList->ItemAt(0)) {
2376 			// TODO: This assumes all entries in the list share the same
2377 			// volume...
2378 			BVolume volume(ref->device);
2379 			if (volume.InitCheck() == B_OK)
2380 				blockSize = volume.BlockSize();
2381 		}
2382 	}
2383 	// File systems like ReiserFS may advertize a large block size, but
2384 	// stuff is still packed into blocks, so clamp maximum block size.
2385 	if (blockSize > 8192)
2386 		blockSize = 8192;
2387 
2388 	int32 num_items = refList->CountItems();
2389 	for (int32 i = 0; i < num_items; i++) {
2390 		entry_ref *ref = refList->ItemAt(i);
2391 		BEntry entry(ref);
2392 		StatStruct statbuf;
2393 		entry.GetStat(&statbuf);
2394 
2395 		if (loopControl->CheckUserCanceled())
2396 			return kUserCanceled;
2397 
2398 		if (S_ISDIR(statbuf.st_mode)) {
2399 			BDirectory dir(&entry);
2400 			dirCount++;
2401 			(*totalSize) += blockSize;
2402 			status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2403 				totalSize, &fileCount, &dirCount);
2404 			if (result != B_OK)
2405 				return result;
2406 		} else {
2407 			fileCount++;
2408 			(*totalSize) += statbuf.st_size + blockSize;
2409 		}
2410 	}
2411 
2412 	*totalCount += (fileCount + dirCount);
2413 	return B_OK;
2414 }
2415 
2416 
2417 status_t
2418 FSGetTrashDir(BDirectory *trashDir, dev_t dev)
2419 {
2420 	BVolume volume(dev);
2421 	status_t result = volume.InitCheck();
2422 	if (result != B_OK)
2423 		return result;
2424 
2425 	BPath path;
2426 	result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2427 	if (result != B_OK)
2428 		return result;
2429 
2430 	result = trashDir->SetTo(path.Path());
2431 	if (result == B_OK) {
2432 		// Directory already exists, we're done
2433 		return B_OK;
2434 	}
2435 
2436 	// The trash directory does not exist yet - change that!
2437 
2438 	result = create_directory(path.Path(), 0755);
2439 	if (result != B_OK)
2440 		return result;
2441 
2442 	result = trashDir->SetTo(path.Path());
2443 	if (result != B_OK)
2444 		return result;
2445 
2446 	// make trash invisible
2447 	StatStruct sbuf;
2448 	trashDir->GetStat(&sbuf);
2449 
2450 	PoseInfo poseInfo;
2451 	poseInfo.fInvisible = true;
2452 	poseInfo.fInitedDirectory = sbuf.st_ino;
2453 	trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2454 		sizeof(PoseInfo));
2455 
2456 	// set trash icon
2457 	size_t size;
2458 	const void* data
2459 		= GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2460 	if (data != NULL)
2461 		trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2462 
2463 	data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2464 	if (data != NULL)
2465 		trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2466 
2467 	data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, R_TrashIcon,
2468 		&size);
2469 	if (data != NULL)
2470 		trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2471 
2472 	return B_OK;
2473 }
2474 
2475 
2476 #if __GNUC__ && __GNUC__ < 3
2477 // obsolete version of FSGetDeskDir retained for bin compat with
2478 // BeIDE and a few other apps that apparently use it
2479 status_t
2480 FSGetDeskDir(BDirectory *deskDir, dev_t)
2481 {
2482 	// since we no longer keep a desktop directory on any volume other
2483 	// than /boot, redirect to FSGetDeskDir ignoring the volume argument
2484 	return FSGetDeskDir(deskDir);
2485 }
2486 #endif
2487 
2488 
2489 status_t
2490 FSGetDeskDir(BDirectory *deskDir)
2491 {
2492 	BPath path;
2493 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2494 	if (result != B_OK)
2495 		return result;
2496 
2497 	result = deskDir->SetTo(path.Path());
2498 	if (result != B_OK)
2499 		return result;
2500 
2501 	size_t size;
2502 	const void* data = GetTrackerResources()->
2503 		LoadResource('ICON', R_DeskIcon, &size);
2504 	if (data != NULL)
2505 		deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2506 
2507 	data = GetTrackerResources()->
2508 		LoadResource('MICN', R_DeskIcon, &size);
2509 	if (data != NULL)
2510 		deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2511 
2512 #ifdef __HAIKU__
2513 	data = GetTrackerResources()->
2514 		LoadResource(B_VECTOR_ICON_TYPE, R_DeskIcon, &size);
2515 	if (data != NULL)
2516 		deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2517 #endif
2518 
2519 	return B_OK;
2520 }
2521 
2522 
2523 status_t
2524 FSGetBootDeskDir(BDirectory *deskDir)
2525 {
2526 	BVolume	bootVol;
2527 	BVolumeRoster().GetBootVolume(&bootVol);
2528 	BPath path;
2529 
2530 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2531 		&bootVol);
2532 	if (result != B_OK)
2533 		return result;
2534 
2535 	return deskDir->SetTo(path.Path());
2536 }
2537 
2538 
2539 static bool
2540 FSIsDirFlavor(const BEntry *entry, directory_which directoryType)
2541 {
2542 	StatStruct dir_stat;
2543 	StatStruct entry_stat;
2544 	BVolume volume;
2545 	BPath path;
2546 
2547 	if (entry->GetStat(&entry_stat) != B_OK)
2548 		return false;
2549 
2550 	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2551 		return false;
2552 
2553 	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2554 		return false;
2555 
2556 	stat(path.Path(), &dir_stat);
2557 
2558 	return dir_stat.st_ino == entry_stat.st_ino
2559 		&& dir_stat.st_dev == entry_stat.st_dev;
2560 }
2561 
2562 
2563 bool
2564 FSIsPrintersDir(const BEntry *entry)
2565 {
2566 	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2567 }
2568 
2569 
2570 bool
2571 FSIsTrashDir(const BEntry *entry)
2572 {
2573 	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2574 }
2575 
2576 
2577 bool
2578 FSIsDeskDir(const BEntry *entry)
2579 {
2580 	BPath path;
2581 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2582 	if (result != B_OK)
2583 		return false;
2584 
2585 	BEntry entryToCompare(path.Path());
2586 	return entryToCompare == *entry;
2587 }
2588 
2589 
2590 bool
2591 FSIsHomeDir(const BEntry *entry)
2592 {
2593 	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2594 }
2595 
2596 
2597 bool
2598 DirectoryMatchesOrContains(const BEntry *entry, directory_which which)
2599 {
2600 	BPath path;
2601 	if (find_directory(which, &path, false, NULL) != B_OK)
2602 		return false;
2603 
2604 	BEntry dirEntry(path.Path());
2605 	if (dirEntry.InitCheck() != B_OK)
2606 		return false;
2607 
2608 	if (dirEntry == *entry)
2609 		// root level match
2610 		return true;
2611 
2612 	BDirectory dir(&dirEntry);
2613 	return dir.Contains(entry);
2614 }
2615 
2616 
2617 bool
2618 DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath,
2619 	directory_which which)
2620 {
2621 	BPath path;
2622 	if (find_directory(which, &path, false, NULL) != B_OK)
2623 		return false;
2624 
2625 	path.Append(additionalPath);
2626 	BEntry dirEntry(path.Path());
2627 	if (dirEntry.InitCheck() != B_OK)
2628 		return false;
2629 
2630 	if (dirEntry == *entry)
2631 		// root level match
2632 		return true;
2633 
2634 	BDirectory dir(&dirEntry);
2635 	return dir.Contains(entry);
2636 }
2637 
2638 
2639 bool
2640 DirectoryMatches(const BEntry *entry, directory_which which)
2641 {
2642 	BPath path;
2643 	if (find_directory(which, &path, false, NULL) != B_OK)
2644 		return false;
2645 
2646 	BEntry dirEntry(path.Path());
2647 	if (dirEntry.InitCheck() != B_OK)
2648 		return false;
2649 
2650 	return dirEntry == *entry;
2651 }
2652 
2653 
2654 bool
2655 DirectoryMatches(const BEntry *entry, const char *additionalPath,
2656 	directory_which which)
2657 {
2658 	BPath path;
2659 	if (find_directory(which, &path, false, NULL) != B_OK)
2660 		return false;
2661 
2662 	path.Append(additionalPath);
2663 	BEntry dirEntry(path.Path());
2664 	if (dirEntry.InitCheck() != B_OK)
2665 		return false;
2666 
2667 	return dirEntry == *entry;
2668 }
2669 
2670 
2671 extern status_t
2672 FSFindTrackerSettingsDir(BPath *path, bool autoCreate)
2673 {
2674 	status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path,
2675 		autoCreate);
2676 	if (result != B_OK)
2677 		return result;
2678 
2679 	path->Append("Tracker");
2680 
2681 	return mkdir(path->Path(), 0777) ? B_OK : errno;
2682 }
2683 
2684 
2685 bool
2686 FSInTrashDir(const entry_ref *ref)
2687 {
2688 	BEntry entry(ref);
2689 	if (entry.InitCheck() != B_OK)
2690 		return false;
2691 
2692 	BDirectory trashDir;
2693 	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2694 		return false;
2695 
2696 	return trashDir.Contains(&entry);
2697 }
2698 
2699 
2700 void
2701 FSEmptyTrash()
2702 {
2703 	if (find_thread("_tracker_empty_trash_") != B_OK) {
2704 		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2705 			B_NORMAL_PRIORITY, NULL));
2706 	}
2707 }
2708 
2709 
2710 status_t
2711 empty_trash(void *)
2712 {
2713 	// empty trash on all mounted volumes
2714 	status_t err = B_OK;
2715 
2716 	TrackerCopyLoopControl loopControl(kTrashState);
2717 
2718 	// calculate the sum total of all items on all volumes in trash
2719 	BObjectList<entry_ref> srcList;
2720 	int32 totalCount = 0;
2721 	off_t totalSize = 0;
2722 
2723 	BVolumeRoster volumeRoster;
2724 	BVolume volume;
2725 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2726 		if (volume.IsReadOnly() || !volume.IsPersistent())
2727 			continue;
2728 
2729 		BDirectory trashDirectory;
2730 		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2731 			continue;
2732 
2733 		BEntry entry;
2734 		trashDirectory.GetEntry(&entry);
2735 
2736 		entry_ref ref;
2737 		entry.GetRef(&ref);
2738 		srcList.AddItem(&ref);
2739 		err = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
2740 			&totalCount, &totalSize);
2741 		if (err != B_OK)
2742 			break;
2743 
2744 		srcList.MakeEmpty();
2745 
2746 		// don't count trash directory itself
2747 		totalCount--;
2748 	}
2749 
2750 	if (err == B_OK) {
2751 		loopControl.Init(totalCount, totalCount);
2752 
2753 		volumeRoster.Rewind();
2754 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2755 			if (volume.IsReadOnly() || !volume.IsPersistent())
2756 				continue;
2757 
2758 			BDirectory trashDirectory;
2759 			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2760 				continue;
2761 
2762 			BEntry entry;
2763 			trashDirectory.GetEntry(&entry);
2764 			err = FSDeleteFolder(&entry, &loopControl, true, false);
2765 		}
2766 	}
2767 
2768 	if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) {
2769 		(new BAlert("",	B_TRANSLATE("Error emptying Trash!"),
2770 			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
2771 			B_WARNING_ALERT))->Go();
2772 	}
2773 
2774 	return B_OK;
2775 }
2776 
2777 
2778 status_t
2779 _DeleteTask(BObjectList<entry_ref> *list, bool confirm)
2780 {
2781 	if (confirm) {
2782 		bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
2783 
2784 		if (!dontMoveToTrash) {
2785 			BAlert* alert = new BAlert("",
2786 				B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2787 				B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
2788 				B_TRANSLATE("Delete"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2789 				B_WARNING_ALERT);
2790 
2791 			alert->SetShortcut(0, B_ESCAPE);
2792 			alert->SetShortcut(1, 'm');
2793 			alert->SetShortcut(2, 'd');
2794 
2795 			switch (alert->Go()) {
2796 				case 0:
2797 					delete list;
2798 					return B_OK;
2799 				case 1:
2800 					FSMoveToTrash(list, NULL, false);
2801 					return B_OK;
2802 			}
2803 		} else {
2804 			BAlert* alert = new BAlert("",
2805 				B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2806 				B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"), NULL,
2807 				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
2808 
2809 			alert->SetShortcut(0, B_ESCAPE);
2810 			alert->SetShortcut(1, 'd');
2811 
2812 			if (!alert->Go()) {
2813 				delete list;
2814 				return B_OK;
2815 			}
2816 		}
2817 	}
2818 
2819 	TrackerCopyLoopControl loopControl(kDeleteState);
2820 
2821 	// calculate the sum total of all items on all volumes in trash
2822 	int32 totalItems = 0;
2823 	int64 totalSize = 0;
2824 
2825 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2826 		&totalSize);
2827 	if (err == B_OK) {
2828 		loopControl.Init(totalItems, totalItems);
2829 
2830 		int32 count = list->CountItems();
2831 		for (int32 index = 0; index < count; index++) {
2832 			entry_ref ref(*list->ItemAt(index));
2833 			BEntry entry(&ref);
2834 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2835 			if (entry.IsDirectory())
2836 				err = FSDeleteFolder(&entry, &loopControl, true, true, true);
2837 			else
2838 				err = entry.Remove();
2839 		}
2840 
2841 		if (err != kTrashCanceled && err != kUserCanceled && err != B_OK)
2842 			(new BAlert("",	B_TRANSLATE("Error deleting items"),
2843 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
2844 				B_WARNING_ALERT))->Go();
2845 	}
2846 
2847 	delete list;
2848 
2849 	return B_OK;
2850 }
2851 
2852 status_t
2853 FSRecursiveCreateFolder(BPath path)
2854 {
2855 	BEntry entry(path.Path());
2856 	if (entry.InitCheck() != B_OK) {
2857 		BPath parentPath;
2858 		status_t err = path.GetParent(&parentPath);
2859 		if (err != B_OK)
2860 			return err;
2861 
2862 		err = FSRecursiveCreateFolder(parentPath);
2863 		if (err != B_OK)
2864 			return err;
2865 	}
2866 
2867 	entry.SetTo(path.Path());
2868 	if (entry.Exists())
2869 		return B_FILE_EXISTS;
2870 	else {
2871 		char name[B_FILE_NAME_LENGTH];
2872 		BDirectory parent;
2873 
2874 		entry.GetParent(&parent);
2875 		entry.GetName(name);
2876 		parent.CreateDirectory(name, NULL);
2877 	}
2878 
2879 	return B_OK;
2880 }
2881 
2882 status_t
2883 _RestoreTask(BObjectList<entry_ref> *list)
2884 {
2885 	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
2886 
2887 	// calculate the sum total of all items that will be restored
2888 	int32 totalItems = 0;
2889 	int64 totalSize = 0;
2890 
2891 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2892 		&totalSize);
2893 	if (err == B_OK) {
2894 		loopControl.Init(totalItems, totalItems);
2895 
2896 		int32 count = list->CountItems();
2897 		for (int32 index = 0; index < count; index++) {
2898 			entry_ref ref(*list->ItemAt(index));
2899 			BEntry entry(&ref);
2900 			BPath originalPath;
2901 
2902 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2903 
2904 			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
2905 				continue;
2906 
2907 			BEntry originalEntry(originalPath.Path());
2908 			BPath parentPath;
2909 			err = originalPath.GetParent(&parentPath);
2910 			if (err != B_OK)
2911 				continue;
2912 			BEntry parentEntry(parentPath.Path());
2913 
2914 			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
2915 				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
2916 					originalEntry.SetTo(originalPath.Path());
2917 					if (entry.InitCheck() != B_OK)
2918 						continue;
2919 				}
2920 			}
2921 
2922 			if (!originalEntry.Exists()) {
2923 				BDirectory dir(parentPath.Path());
2924 				if (dir.InitCheck() == B_OK) {
2925 					char leafName[B_FILE_NAME_LENGTH];
2926 					originalEntry.GetName(leafName);
2927 					if (entry.MoveTo(&dir, leafName) == B_OK) {
2928 						BNode node(&entry);
2929 						if (node.InitCheck() == B_OK)
2930 							node.RemoveAttr(kAttrOriginalPath);
2931 					}
2932 				}
2933 			}
2934 
2935 			err = loopControl.CheckUserCanceled();
2936 			if (err != B_OK)
2937 				break;
2938 		}
2939 	}
2940 
2941 	delete list;
2942 
2943 	return err;
2944 }
2945 
2946 void
2947 FSCreateTrashDirs()
2948 {
2949 	BVolume volume;
2950 	BVolumeRoster roster;
2951 
2952 	roster.Rewind();
2953 	while (roster.GetNextVolume(&volume) == B_OK) {
2954 		if (volume.IsReadOnly() || !volume.IsPersistent())
2955 			continue;
2956 
2957 		BDirectory trashDir;
2958 		FSGetTrashDir(&trashDir, volume.Device());
2959 	}
2960 }
2961 
2962 
2963 status_t
2964 FSCreateNewFolder(const entry_ref *ref)
2965 {
2966 	node_ref node;
2967 	node.device = ref->device;
2968 	node.node = ref->directory;
2969 
2970 	BDirectory dir(&node);
2971 	status_t result = dir.InitCheck();
2972 	if (result != B_OK)
2973 		return result;
2974 
2975 	// ToDo: is that really necessary here?
2976 	BString name(ref->name);
2977 	FSMakeOriginalName(name, &dir, "-");
2978 
2979 	BDirectory newDir;
2980 	result = dir.CreateDirectory(name.String(), &newDir);
2981 	if (result != B_OK)
2982 		return result;
2983 
2984 	BNodeInfo nodeInfo(&newDir);
2985 	nodeInfo.SetType(B_DIR_MIMETYPE);
2986 
2987 	return result;
2988 }
2989 
2990 
2991 status_t
2992 FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef,
2993 	node_ref *newNode)
2994 {
2995 	BDirectory dir(dirNode);
2996 	status_t result = dir.InitCheck();
2997 	if (result == B_OK) {
2998 		char name[B_FILE_NAME_LENGTH];
2999 		strcpy(name, B_TRANSLATE("New folder"));
3000 
3001 		int32 fnum = 1;
3002 		while (dir.Contains(name)) {
3003 			// if base name already exists then add a number
3004 			// ToDo:
3005 			// move this logic ot FSMakeOriginalName
3006 			if (++fnum > 9) {
3007 				snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"),
3008 					fnum);
3009 			} else {
3010 				snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"),
3011 					fnum);
3012 			}
3013 		}
3014 
3015 		BDirectory newDir;
3016 		result = dir.CreateDirectory(name, &newDir);
3017 		if (result == B_OK) {
3018 			BEntry entry;
3019 			newDir.GetEntry(&entry);
3020 			entry.GetRef(newRef);
3021 			entry.GetNodeRef(newNode);
3022 
3023 			BNodeInfo nodeInfo(&newDir);
3024 			nodeInfo.SetType(B_DIR_MIMETYPE);
3025 
3026 			// add undo item
3027 			NewFolderUndo undo(*newRef);
3028 			return B_OK;
3029 		}
3030 	}
3031 
3032 	BAlert* alert = new BAlert("",
3033 		B_TRANSLATE("Sorry, could not create a new folder."),
3034 		B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3035 	alert->SetShortcut(0, B_ESCAPE);
3036 	alert->Go();
3037 	return result;
3038 }
3039 
3040 
3041 ReadAttrResult
3042 ReadAttr(const BNode *node, const char *hostAttrName,
3043 	const char *foreignAttrName, type_code type, off_t offset, void *buffer,
3044 	size_t length, void (*swapFunc)(void *), bool isForeign)
3045 {
3046 	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3047 			length) == (ssize_t)length) {
3048 		return kReadAttrNativeOK;
3049 	}
3050 
3051 	// PRINT(("trying %s\n", foreignAttrName));
3052 	// try the other endianness
3053 	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3054 			!= (ssize_t)length) {
3055 		return kReadAttrFailed;
3056 	}
3057 
3058 	// PRINT(("got %s\n", foreignAttrName));
3059 	if (!swapFunc)
3060 		return kReadAttrForeignOK;
3061 
3062 	(swapFunc)(buffer);
3063 		// run the endian swapper
3064 
3065 	return kReadAttrForeignOK;
3066 }
3067 
3068 
3069 ReadAttrResult
3070 GetAttrInfo(const BNode *node, const char *hostAttrName,
3071 	const char *foreignAttrName, type_code *type, size_t *size)
3072 {
3073 	attr_info info;
3074 
3075 	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3076 		if (type)
3077 			*type = info.type;
3078 		if (size)
3079 			*size = (size_t)info.size;
3080 
3081 		return kReadAttrNativeOK;
3082 	}
3083 
3084 	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3085 		if (type)
3086 			*type = info.type;
3087 		if (size)
3088 			*size = (size_t)info.size;
3089 
3090 		return kReadAttrForeignOK;
3091 	}
3092 	return kReadAttrFailed;
3093 }
3094 
3095 // launching code
3096 
3097 static status_t
3098 TrackerOpenWith(const BMessage *refs)
3099 {
3100 	BMessage clone(*refs);
3101 	ASSERT(dynamic_cast<TTracker *>(be_app));
3102 	ASSERT(clone.what);
3103 	clone.AddInt32("launchUsingSelector", 0);
3104 	// runs the Open With window
3105 	be_app->PostMessage(&clone);
3106 
3107 	return B_OK;
3108 }
3109 
3110 
3111 static void
3112 AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on),
3113 	const entry_ref *appRef, const BMessage *refs, bool openWithOK)
3114 {
3115 	BMessage *task = new BMessage;
3116 	task->AddPointer("function", (void *)func);
3117 	task->AddMessage("refs", refs);
3118 	task->AddBool("openWithOK", openWithOK);
3119 	if (appRef != NULL)
3120 		task->AddRef("appRef", appRef);
3121 
3122 	extern BLooper *gLaunchLooper;
3123 	gLaunchLooper->PostMessage(task);
3124 }
3125 
3126 static bool
3127 SniffIfGeneric(const entry_ref *ref)
3128 {
3129 	BNode node(ref);
3130 	char type[B_MIME_TYPE_LENGTH];
3131 	BNodeInfo info(&node);
3132 	if (info.GetType(type) == B_OK
3133 		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3134 		// already has a type and it's not octet stream
3135 		return false;
3136 	}
3137 
3138 	BPath path(ref);
3139 	if (path.Path()) {
3140 		// force a mimeset
3141 		node.RemoveAttr(kAttrMIMEType);
3142 		update_mime_info(path.Path(), 0, 1, 1);
3143 	}
3144 
3145 	return true;
3146 }
3147 
3148 static void
3149 SniffIfGeneric(const BMessage *refs)
3150 {
3151 	entry_ref ref;
3152 	for (int32 index = 0; ; index++) {
3153 		if (refs->FindRef("refs", index, &ref) != B_OK)
3154 			break;
3155 		SniffIfGeneric(&ref);
3156 	}
3157 }
3158 
3159 static void
3160 _TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs,
3161 	bool openWithOK)
3162 {
3163 	team_id team;
3164 
3165 	status_t error = B_ERROR;
3166 	BString alertString;
3167 
3168 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3169 		error = be_roster->Launch(appRef, refs, &team);
3170 		if (error == B_ALREADY_RUNNING)
3171 			// app already running, not really an error
3172 			error = B_OK;
3173 
3174 		if (error == B_OK)
3175 			break;
3176 
3177 		if (mimesetIt > 0)
3178 			break;
3179 
3180 		// failed to open, try mimesetting the refs and launching again
3181 		SniffIfGeneric(refs);
3182 	}
3183 
3184 	if (error == B_OK) {
3185 		// close possible parent window, if specified
3186 		const node_ref *nodeToClose = 0;
3187 		int32 numBytes;
3188 		refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3189 			(const void **)&nodeToClose, &numBytes);
3190 		if (nodeToClose)
3191 			dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose);
3192 	} else {
3193 		alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3194 		alertString.ReplaceFirst("%name", appRef->name);
3195 		alertString.ReplaceFirst("%error", strerror(error));
3196 		if (refs && openWithOK && error != B_SHUTTING_DOWN) {
3197 			alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3198 			BAlert* alert = new BAlert("", alertString.String(),
3199 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"),	0, B_WIDTH_AS_USUAL,
3200 				B_WARNING_ALERT);
3201 			alert->SetShortcut(0, B_ESCAPE);
3202 			if (alert->Go() == 1)
3203 				error = TrackerOpenWith(refs);
3204 		} else {
3205 			BAlert* alert = new BAlert("", alertString.String(),
3206 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3207 			alert->SetShortcut(0, B_ESCAPE);
3208 			alert->Go();
3209 		}
3210 	}
3211 }
3212 
3213 extern "C" char** environ;
3214 
3215 extern "C" status_t _kern_load_image(const char * const *flatArgs,
3216 	size_t flatArgsSize, int32 argCount, int32 envCount, int32 priority,
3217 	uint32 flags, port_id errorPort, uint32 errorToken);
3218 extern "C" status_t __flatten_process_args(const char * const *args,
3219 	int32 argCount, const char * const *env, int32 envCount, char ***_flatArgs,
3220 	size_t *_flatSize);
3221 
3222 
3223 static status_t
3224 LoaderErrorDetails(const entry_ref *app, BString &details)
3225 {
3226 	BPath path;
3227 	BEntry appEntry(app, true);
3228 
3229 	status_t result = appEntry.GetPath(&path);
3230 	if (result != B_OK)
3231 		return result;
3232 
3233 	char *argv[2] = { const_cast<char *>(path.Path()), 0};
3234 
3235 	port_id errorPort = create_port(1, "Tracker loader error");
3236 
3237 	// count environment variables
3238 	uint32 envCount = 0;
3239 	while (environ[envCount] != NULL)
3240 		envCount++;
3241 
3242 	char** flatArgs = NULL;
3243 	size_t flatArgsSize;
3244 	result = __flatten_process_args((const char **)argv, 1,
3245 		environ, envCount, &flatArgs, &flatArgsSize);
3246 	if (result != B_OK)
3247 		return result;
3248 
3249 	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3250 		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3251 	if (result == B_OK) {
3252 		// we weren't supposed to be able to start the application...
3253 		return B_ERROR;
3254 	}
3255 
3256 	// read error message from port and construct details string
3257 
3258 	ssize_t bufferSize;
3259 
3260 	do {
3261 		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3262 	} while (bufferSize == B_INTERRUPTED);
3263 
3264 	if (bufferSize <= B_OK) {
3265 		delete_port(errorPort);
3266 		return bufferSize;
3267 	}
3268 
3269 	uint8 *buffer = (uint8 *)malloc(bufferSize);
3270 	if (buffer == NULL) {
3271 		delete_port(errorPort);
3272 		return B_NO_MEMORY;
3273 	}
3274 
3275 	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3276 		B_RELATIVE_TIMEOUT, 0);
3277 	delete_port(errorPort);
3278 
3279 	if (bufferSize < B_OK) {
3280 		free(buffer);
3281 		return bufferSize;
3282 	}
3283 
3284 	BMessage message;
3285 	result = message.Unflatten((const char *)buffer);
3286 	free(buffer);
3287 
3288 	if (result != B_OK)
3289 		return result;
3290 
3291 	int32 errorCode = B_ERROR;
3292 	result = message.FindInt32("error", &errorCode);
3293 	if (result != B_OK)
3294 		return result;
3295 
3296 	const char *detailName = NULL;
3297 	switch (errorCode) {
3298 		case B_MISSING_LIBRARY:
3299 			detailName = "missing library";
3300 			break;
3301 
3302 		case B_MISSING_SYMBOL:
3303 			detailName = "missing symbol";
3304 			break;
3305 	}
3306 
3307 	if (detailName == NULL)
3308 		return B_ERROR;
3309 
3310 	const char *detail;
3311 	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3312 			i++) {
3313 		if (i > 0)
3314 			details += ", ";
3315 		details += detail;
3316 	}
3317 
3318 	return B_OK;
3319 }
3320 
3321 
3322 static void
3323 _TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs,
3324 	bool openWithOK)
3325 {
3326 	BMessage copyOfRefs(*refs);
3327 
3328 	entry_ref documentRef;
3329 	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK)
3330 		// nothing to launch, we are done
3331 		return;
3332 
3333 	status_t error = B_ERROR;
3334 	entry_ref app;
3335 	BMessage *refsToPass = NULL;
3336 	BString alertString;
3337 	const char *alternative = 0;
3338 
3339 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3340 		alertString = "";
3341 		error = be_roster->FindApp(&documentRef, &app);
3342 
3343 		if (error != B_OK && mimesetIt == 0) {
3344 			SniffIfGeneric(&copyOfRefs);
3345 			continue;
3346 		}
3347 
3348 		if (error != B_OK) {
3349 			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3350 				"open \"%name\" (%error). "));
3351 			alertString.ReplaceFirst("%name", documentRef.name);
3352 			alertString.ReplaceFirst("%error", strerror(error));
3353 			if (openWithOK)
3354 				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3355 
3356 			break;
3357 		} else {
3358 			BEntry appEntry(&app, true);
3359 			for (int32 index = 0;;) {
3360 				// remove the app itself from the refs received so we don't try
3361 				// to open ourselves
3362 				entry_ref ref;
3363 				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3364 					break;
3365 
3366 				// deal with symlinks properly
3367 				BEntry documentEntry(&ref, true);
3368 				if (appEntry == documentEntry) {
3369 					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3370 					copyOfRefs.RemoveData("refs", index);
3371 				} else {
3372 					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3373 					index++;
3374 				}
3375 			}
3376 
3377 			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3378 			team_id team;
3379 			error = be_roster->Launch(&app, refsToPass, &team);
3380 			if (error == B_ALREADY_RUNNING)
3381 				// app already running, not really an error
3382 				error = B_OK;
3383 			if (error == B_OK || mimesetIt != 0)
3384 				break;
3385 
3386 			SniffIfGeneric(&copyOfRefs);
3387 		}
3388 	}
3389 
3390 	if (error != B_OK && alertString.Length() == 0) {
3391 		BString loaderErrorString;
3392 		bool openedDocuments = true;
3393 
3394 		if (!refsToPass) {
3395 			// we just double clicked the app itself, do not offer to
3396 			// find a handling app
3397 			openWithOK = false;
3398 			openedDocuments = false;
3399 		}
3400 
3401 		if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3402 			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3403 				"The file is mistakenly marked as executable. "));
3404 			alertString.ReplaceFirst("%name", app.name);
3405 
3406 			if (!openWithOK) {
3407 				// offer the possibility to change the permissions
3408 
3409 				alertString << B_TRANSLATE("\nShould this be fixed?");
3410 				BAlert* alert = new BAlert("", alertString.String(),
3411 					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3412 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3413 				alert->SetShortcut(0, B_ESCAPE);
3414 				if (alert->Go() == 1) {
3415 					BEntry entry(&documentRef);
3416 					mode_t permissions;
3417 
3418 					error = entry.GetPermissions(&permissions);
3419 					if (error == B_OK) {
3420 						error = entry.SetPermissions(permissions
3421 							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3422 					}
3423 					if (error == B_OK) {
3424 						// we updated the permissions, so let's try again
3425 						_TrackerLaunchDocuments(NULL, refs, false);
3426 						return;
3427 					} else {
3428 						alertString.SetTo(B_TRANSLATE("Could not update "
3429 							"permissions of file \"%name\". %error"));
3430 						alertString.ReplaceFirst("%name", app.name);
3431 						alertString.ReplaceFirst("%error", strerror(error));
3432 					}
3433 				} else
3434 					return;
3435 			}
3436 
3437 			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3438 		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3439 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3440 				"because application \"%app\" is in the Trash. "));
3441 			alertString.ReplaceFirst("%document", documentRef.name);
3442 			alertString.ReplaceFirst("%app", app.name);
3443 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3444 		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3445 			alertString.SetTo(
3446 				B_TRANSLATE("Could not open \"%name\" (%error). "));
3447 			alertString.ReplaceFirst("%name", documentRef.name);
3448 			alertString.ReplaceFirst("%error", strerror(error));
3449 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3450 		} else if (error == B_MISSING_SYMBOL
3451 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3452 			if (openedDocuments) {
3453 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3454 					"with application \"%app\" (Missing symbol: %symbol). \n"));
3455 				alertString.ReplaceFirst("%document", documentRef.name);
3456 				alertString.ReplaceFirst("%app", app.name);
3457 				alertString.ReplaceFirst("%symbol", loaderErrorString.String());
3458 			} else {
3459 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3460 					"(Missing symbol: %symbol). \n"));
3461 				alertString.ReplaceFirst("%document", documentRef.name);
3462 				alertString.ReplaceFirst("%symbol", loaderErrorString.String());
3463 			}
3464 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3465 		} else if (error == B_MISSING_LIBRARY
3466 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3467 			if (openedDocuments) {
3468 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3469 					"with application \"%app\" (Missing libraries: %library). "
3470 					"\n"));
3471 				alertString.ReplaceFirst("%document", documentRef.name);
3472 				alertString.ReplaceFirst("%app", app.name);
3473 				alertString.ReplaceFirst("%library", loaderErrorString.String());
3474 			} else {
3475 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3476 					"(Missing libraries: %library). \n"));
3477 				alertString.ReplaceFirst("%document", documentRef.name);
3478 				alertString.ReplaceFirst("%library", loaderErrorString.String());
3479 			}
3480 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3481 		} else {
3482 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3483 				"application \"%app\" (%error). "));
3484 				alertString.ReplaceFirst("%document", documentRef.name);
3485 				alertString.ReplaceFirst("%app", app.name);
3486 				alertString.ReplaceFirst("%error", strerror(error));
3487 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3488 		}
3489 	}
3490 
3491 	if (error != B_OK) {
3492 		if (openWithOK) {
3493 			ASSERT(alternative);
3494 			alertString << alternative;
3495 			BAlert* alert = new BAlert("", alertString.String(),
3496 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"),	0, B_WIDTH_AS_USUAL,
3497 				B_WARNING_ALERT);
3498 			alert->SetShortcut(0, B_ESCAPE);
3499 			if (alert->Go() == 1)
3500 				error = TrackerOpenWith(refs);
3501 		} else {
3502 			BAlert* alert = new BAlert("", alertString.String(),
3503 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3504 			alert->SetShortcut(0, B_ESCAPE);
3505 			alert->Go();
3506 		}
3507 	}
3508 }
3509 
3510 // the following three calls don't return any reasonable error codes,
3511 // should fix that, making them void
3512 
3513 status_t
3514 TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async,
3515 	bool openWithOK)
3516 {
3517 	if (!async)
3518 		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3519 	else {
3520 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3521 			openWithOK);
3522 	}
3523 
3524 	return B_OK;
3525 }
3526 
3527 status_t
3528 TrackerLaunch(const entry_ref *appRef, bool async)
3529 {
3530 	if (!async)
3531 		_TrackerLaunchAppWithDocuments(appRef, 0, false);
3532 	else
3533 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3534 
3535 	return B_OK;
3536 }
3537 
3538 status_t
3539 TrackerLaunch(const BMessage *refs, bool async, bool openWithOK)
3540 {
3541 	if (!async)
3542 		_TrackerLaunchDocuments(0, refs, openWithOK);
3543 	else
3544 		AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK);
3545 
3546 	return B_OK;
3547 }
3548 
3549 status_t
3550 LaunchBrokenLink(const char *signature, const BMessage *refs)
3551 {
3552 	// This call is to support a hacky workaround for double-clicking
3553 	// broken refs for cifs
3554 	be_roster->Launch(signature, const_cast<BMessage *>(refs));
3555 	return B_OK;
3556 }
3557 
3558 // external launch calls; need to be robust, work if Tracker is not running
3559 
3560 #if !B_BEOS_VERSION_DANO
3561 _IMPEXP_TRACKER
3562 #endif
3563 status_t
3564 FSLaunchItem(const entry_ref *application, const BMessage *refsReceived,
3565 	bool async, bool openWithOK)
3566 {
3567 	return TrackerLaunch(application, refsReceived, async, openWithOK);
3568 }
3569 
3570 
3571 #if !B_BEOS_VERSION_DANO
3572 _IMPEXP_TRACKER
3573 #endif
3574 status_t
3575 FSOpenWith(BMessage *listOfRefs)
3576 {
3577 	status_t result = B_ERROR;
3578 	listOfRefs->what = B_REFS_RECEIVED;
3579 
3580 	if (dynamic_cast<TTracker *>(be_app))
3581 		result = TrackerOpenWith(listOfRefs);
3582 	else
3583 		ASSERT(!"not yet implemented");
3584 
3585 	return result;
3586 }
3587 
3588 // legacy calls, need for compatibility
3589 
3590 void
3591 FSOpenWithDocuments(const entry_ref *executable, BMessage *documents)
3592 {
3593 	TrackerLaunch(executable, documents, true);
3594 	delete documents;
3595 }
3596 
3597 status_t
3598 FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs)
3599 {
3600 	BMessage temp(B_REFS_RECEIVED);
3601 	if (!listOfRefs) {
3602 		ASSERT(ref);
3603 		temp.AddRef("refs", ref);
3604 		listOfRefs = &temp;
3605 	}
3606 	FSOpenWith(listOfRefs);
3607 	return B_OK;
3608 }
3609 
3610 status_t
3611 FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async)
3612 {
3613 	if (message)
3614 		message->what = B_REFS_RECEIVED;
3615 
3616 	status_t result = TrackerLaunch(ref, message, async, true);
3617 	delete message;
3618 	return result;
3619 }
3620 
3621 
3622 void
3623 FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace)
3624 {
3625 	FSLaunchItem(ref, message, workspace, true);
3626 }
3627 
3628 // Get the original path of an entry in the trash
3629 status_t
3630 FSGetOriginalPath(BEntry *entry, BPath *result)
3631 {
3632 	status_t err;
3633 	entry_ref ref;
3634 	err = entry->GetRef(&ref);
3635 	if (err != B_OK)
3636 		return err;
3637 
3638 	// Only call the routine for entries in the trash
3639 	if (!FSInTrashDir(&ref))
3640 		return B_ERROR;
3641 
3642 	BNode node(entry);
3643 	BString originalPath;
3644 	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3645 		// We're in luck, the entry has the original path in an attribute
3646 		err = result->SetTo(originalPath.String());
3647 		return err;
3648 	}
3649 
3650 	// Iterate the parent directories to find one with
3651 	// the original path attribute
3652 	BEntry parent(*entry);
3653 	err = parent.InitCheck();
3654 	if (err != B_OK)
3655 		return err;
3656 
3657 	// walk up the directory structure until we find a node
3658 	// with original path attribute
3659 	do {
3660 		// move to the parent of this node
3661 		err = parent.GetParent(&parent);
3662 		if (err != B_OK)
3663 			return err;
3664 
3665 		// return if we are at the root of the trash
3666 		if (FSIsTrashDir(&parent))
3667 			return B_ENTRY_NOT_FOUND;
3668 
3669 		// get the parent as a node
3670 		err = node.SetTo(&parent);
3671 		if (err != B_OK)
3672 			return err;
3673 	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3674 
3675 	// Found the attribute, figure out there this file
3676 	// used to live, based on the successfully-read attribute
3677 	err = result->SetTo(originalPath.String());
3678 	if (err != B_OK)
3679 		return err;
3680 
3681 	BPath path, pathParent;
3682 	err = parent.GetPath(&pathParent);
3683 	if (err != B_OK)
3684 		return err;
3685 	err = entry->GetPath(&path);
3686 	if (err != B_OK)
3687 		return err;
3688 	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3689 		// compute the new path by appending the offset of
3690 		// the item we are locating, to the original path
3691 		// of the parent
3692 	return B_OK;
3693 }
3694 
3695 directory_which
3696 WellKnowEntryList::Match(const node_ref *node)
3697 {
3698 	const WellKnownEntry *result = MatchEntry(node);
3699 	if (result)
3700 		return result->which;
3701 
3702 	return (directory_which)-1;
3703 }
3704 
3705 const WellKnowEntryList::WellKnownEntry *
3706 WellKnowEntryList::MatchEntry(const node_ref *node)
3707 {
3708 	if (!self)
3709 		self = new WellKnowEntryList();
3710 
3711 	return self->MatchEntryCommon(node);
3712 }
3713 
3714 const WellKnowEntryList::WellKnownEntry *
3715 WellKnowEntryList::MatchEntryCommon(const node_ref *node)
3716 {
3717 	uint32 count = entries.size();
3718 	for (uint32 index = 0; index < count; index++)
3719 		if (*node == entries[index].node)
3720 			return &entries[index];
3721 
3722 	return NULL;
3723 }
3724 
3725 
3726 void
3727 WellKnowEntryList::Quit()
3728 {
3729 	delete self;
3730 	self = NULL;
3731 }
3732 
3733 
3734 void
3735 WellKnowEntryList::AddOne(directory_which which, const char *name)
3736 {
3737 	BPath path;
3738 	if (find_directory(which, &path, true) != B_OK)
3739 		return;
3740 
3741 	BEntry entry(path.Path(), true);
3742 	node_ref node;
3743 	if (entry.GetNodeRef(&node) != B_OK)
3744 		return;
3745 
3746 	entries.push_back(WellKnownEntry(&node, which, name));
3747 }
3748 
3749 
3750 void
3751 WellKnowEntryList::AddOne(directory_which which, directory_which base,
3752 	const char *extra, const char *name)
3753 {
3754 	BPath path;
3755 	if (find_directory(base, &path, true) != B_OK)
3756 		return;
3757 
3758 	path.Append(extra);
3759 	BEntry entry(path.Path(), true);
3760 	node_ref node;
3761 	if (entry.GetNodeRef(&node) != B_OK)
3762 		return;
3763 
3764 	entries.push_back(WellKnownEntry(&node, which, name));
3765 }
3766 
3767 
3768 void
3769 WellKnowEntryList::AddOne(directory_which which, const char *path,
3770 	const char *name)
3771 {
3772 	BEntry entry(path, true);
3773 	node_ref node;
3774 	if (entry.GetNodeRef(&node) != B_OK)
3775 		return;
3776 
3777 	entries.push_back(WellKnownEntry(&node, which, name));
3778 }
3779 
3780 
3781 WellKnowEntryList::WellKnowEntryList()
3782 {
3783 #ifdef __HAIKU__
3784 	AddOne(B_SYSTEM_DIRECTORY, "system");
3785 #else
3786 	AddOne(B_BEOS_DIRECTORY, "beos");
3787 	AddOne(B_BEOS_SYSTEM_DIRECTORY, "system");
3788 #endif
3789 	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
3790 	AddOne(B_USER_DIRECTORY, "home");
3791 
3792 	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
3793 	AddOne(B_COMMON_FONTS_DIRECTORY, "fonts");
3794 	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
3795 
3796 	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
3797 	AddOne(B_APPS_DIRECTORY, "apps");
3798 	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
3799 		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
3800 
3801 	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
3802 	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
3803 	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
3804 		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
3805 
3806 	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
3807 		"mail");
3808 
3809 	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
3810 		"queries", "queries");
3811 
3812 
3813 
3814 	AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop");
3815 	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
3816 		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
3817 
3818 	AddOne(B_USER_CONFIG_DIRECTORY, "config");
3819 
3820 	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
3821 		"people", "people");
3822 
3823 	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
3824 		"downloads", "downloads");
3825 }
3826 
3827 WellKnowEntryList *WellKnowEntryList::self = NULL;
3828 
3829 } // namespace BPrivate
3830