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