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