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