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