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