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