xref: /haiku/src/kits/tracker/FSUtils.cpp (revision 9d6d3fcf5fe8308cd020cecf89dede440346f8c4)
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 
2149 	if (data)
2150 		deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2151 
2152 	data = GetTrackerResources()->
2153 		LoadResource('MICN', kResDeskIcon, &size);
2154 
2155 	if (data)
2156 		deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2157 
2158 	data = GetTrackerResources()->
2159 		LoadResource(B_VECTOR_ICON_TYPE, kResDeskIcon, &size);
2160 
2161 	if (data)
2162 		deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2163 
2164 	return B_OK;
2165 }
2166 
2167 
2168 status_t
2169 FSGetBootDeskDir(BDirectory *deskDir)
2170 {
2171 	BVolume	bootVol;
2172 	BVolumeRoster().GetBootVolume(&bootVol);
2173 	BPath path;
2174 
2175 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &bootVol);
2176 	if (result != B_OK)
2177 		return result;
2178 
2179 	return deskDir->SetTo(path.Path());
2180 }
2181 
2182 
2183 static bool
2184 FSIsDirFlavor(const BEntry *entry, directory_which directoryType)
2185 {
2186 	StatStruct dir_stat;
2187 	StatStruct entry_stat;
2188 	BVolume volume;
2189 	BPath path;
2190 
2191 	if (entry->GetStat(&entry_stat) != B_OK)
2192 		return false;
2193 
2194 	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2195 		return false;
2196 
2197 	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2198 		return false;
2199 
2200 	stat(path.Path(), &dir_stat);
2201 
2202 	return dir_stat.st_ino == entry_stat.st_ino
2203 		&& dir_stat.st_dev == entry_stat.st_dev;
2204 }
2205 
2206 
2207 bool
2208 FSIsPrintersDir(const BEntry *entry)
2209 {
2210 	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2211 }
2212 
2213 
2214 bool
2215 FSIsTrashDir(const BEntry *entry)
2216 {
2217 	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2218 }
2219 
2220 
2221 bool
2222 FSIsDeskDir(const BEntry *entry, dev_t device)
2223 {
2224 	BVolume volume(device);
2225 	status_t result = volume.InitCheck();
2226 	if (result != B_OK)
2227 		return false;
2228 
2229 	BPath path;
2230 	result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume);
2231 	if (result != B_OK)
2232 		return false;
2233 
2234 	BEntry entryToCompare(path.Path());
2235 	return entryToCompare == *entry;
2236 }
2237 
2238 
2239 bool
2240 FSIsDeskDir(const BEntry *entry)
2241 {
2242 	entry_ref ref;
2243 	if (entry->GetRef(&ref) != B_OK)
2244 		return false;
2245 
2246 	return FSIsDeskDir(entry, ref.device);
2247 }
2248 
2249 
2250 bool
2251 FSIsSystemDir(const BEntry *entry)
2252 {
2253 	return FSIsDirFlavor(entry, B_BEOS_SYSTEM_DIRECTORY);
2254 }
2255 
2256 
2257 bool
2258 FSIsBeOSDir(const BEntry *entry)
2259 {
2260 	return FSIsDirFlavor(entry, B_BEOS_DIRECTORY);
2261 }
2262 
2263 
2264 bool
2265 FSIsHomeDir(const BEntry *entry)
2266 {
2267 	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2268 }
2269 
2270 
2271 bool
2272 DirectoryMatchesOrContains(const BEntry *entry, directory_which which)
2273 {
2274 	BPath path;
2275 	if (find_directory(which, &path, false, NULL) != B_OK)
2276 		return false;
2277 
2278 	BEntry dirEntry(path.Path());
2279 	if (dirEntry.InitCheck() != B_OK)
2280 		return false;
2281 
2282 	if (dirEntry == *entry)
2283 		// root level match
2284 		return true;
2285 
2286 	BDirectory dir(&dirEntry);
2287 	return dir.Contains(entry);
2288 }
2289 
2290 
2291 bool
2292 DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath,
2293 	directory_which which)
2294 {
2295 	BPath path;
2296 	if (find_directory(which, &path, false, NULL) != B_OK)
2297 		return false;
2298 
2299 	path.Append(additionalPath);
2300 	BEntry dirEntry(path.Path());
2301 	if (dirEntry.InitCheck() != B_OK)
2302 		return false;
2303 
2304 	if (dirEntry == *entry)
2305 		// root level match
2306 		return true;
2307 
2308 	BDirectory dir(&dirEntry);
2309 	return dir.Contains(entry);
2310 }
2311 
2312 
2313 bool
2314 DirectoryMatches(const BEntry *entry, directory_which which)
2315 {
2316 	BPath path;
2317 	if (find_directory(which, &path, false, NULL) != B_OK)
2318 		return false;
2319 
2320 	BEntry dirEntry(path.Path());
2321 	if (dirEntry.InitCheck() != B_OK)
2322 		return false;
2323 
2324 	return dirEntry == *entry;
2325 }
2326 
2327 
2328 bool
2329 DirectoryMatches(const BEntry *entry, const char *additionalPath, directory_which which)
2330 {
2331 	BPath path;
2332 	if (find_directory(which, &path, false, NULL) != B_OK)
2333 		return false;
2334 
2335 	path.Append(additionalPath);
2336 	BEntry dirEntry(path.Path());
2337 	if (dirEntry.InitCheck() != B_OK)
2338 		return false;
2339 
2340 	return dirEntry == *entry;
2341 }
2342 
2343 
2344 extern status_t
2345 FSFindTrackerSettingsDir(BPath *path, bool autoCreate)
2346 {
2347 	status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path, autoCreate);
2348 	if (result != B_OK)
2349 		return result;
2350 
2351 	path->Append("Tracker");
2352 
2353 	return mkdir(path->Path(), 0777) ? B_OK : errno;
2354 }
2355 
2356 
2357 bool
2358 FSInTrashDir(const entry_ref *ref)
2359 {
2360 	BEntry entry(ref);
2361 	if (entry.InitCheck() != B_OK)
2362 		return false;
2363 
2364 	BDirectory trashDir;
2365 	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2366 		return false;
2367 
2368 	return trashDir.Contains(&entry);
2369 }
2370 
2371 
2372 void
2373 FSEmptyTrash()
2374 {
2375 	if (find_thread("_tracker_empty_trash_") != B_OK) {
2376 		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2377 			B_NORMAL_PRIORITY, NULL));
2378 	}
2379 }
2380 
2381 
2382 status_t
2383 empty_trash(void *)
2384 {
2385 	// empty trash on all mounted volumes
2386 	status_t err = B_OK;
2387 
2388 	thread_id thread = find_thread(NULL);
2389 	if (gStatusWindow)
2390 		gStatusWindow->CreateStatusItem(thread, kTrashState);
2391 
2392 	// calculate the sum total of all items on all volumes in trash
2393 	BObjectList<entry_ref> srcList;
2394 	int32 totalCount = 0;
2395 	off_t totalSize = 0;
2396 
2397 	BVolumeRoster volumeRoster;
2398 	BVolume volume;
2399 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2400 		if (volume.IsReadOnly() || !volume.IsPersistent())
2401 			continue;
2402 
2403 		BDirectory trashDirectory;
2404 		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2405 			continue;
2406 
2407 		BEntry entry;
2408 		trashDirectory.GetEntry(&entry);
2409 
2410 		entry_ref ref;
2411 		entry.GetRef(&ref);
2412 		srcList.AddItem(&ref);
2413 		err = CalcItemsAndSize(&srcList, &totalCount, &totalSize);
2414 		if (err != B_OK)
2415 			break;
2416 
2417 		srcList.MakeEmpty();
2418 
2419 		// don't count trash directory itself
2420 		totalCount--;
2421 	}
2422 
2423 	if (err == B_OK) {
2424 		if (gStatusWindow)
2425 			gStatusWindow->InitStatusItem(thread, totalCount, totalCount);
2426 
2427 		volumeRoster.Rewind();
2428 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2429 			TrackerCopyLoopControl loopControl(thread);
2430 
2431 			if (volume.IsReadOnly() || !volume.IsPersistent())
2432 				continue;
2433 
2434 			BDirectory trashDirectory;
2435 			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2436 				continue;
2437 
2438 			BEntry entry;
2439 			trashDirectory.GetEntry(&entry);
2440 			err = FSDeleteFolder(&entry, &loopControl, true, false);
2441 		}
2442 	}
2443 
2444 	if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) {
2445 		(new BAlert("", "Error emptying Trash!", "OK", NULL, NULL,
2446 			B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2447 	}
2448 
2449 	if (gStatusWindow)
2450 		gStatusWindow->RemoveStatusItem(find_thread(NULL));
2451 
2452 	return B_OK;
2453 }
2454 
2455 
2456 status_t
2457 _DeleteTask(BObjectList<entry_ref> *list, bool confirm)
2458 {
2459 	if (confirm) {
2460 		bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
2461 
2462 		if (!dontMoveToTrash) {
2463 			BAlert *alert = new BAlert("", kDeleteConfirmationStr,
2464 				"Cancel", "Move to Trash", "Delete", B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2465 				B_WARNING_ALERT);
2466 
2467 			alert->SetShortcut(0, B_ESCAPE);
2468 			alert->SetShortcut(1, 'm');
2469 			alert->SetShortcut(2, 'd');
2470 
2471 			switch (alert->Go()) {
2472 				case 0:
2473 					delete list;
2474 					return B_OK;
2475 				case 1:
2476 					FSMoveToTrash(list, NULL, false);
2477 					return B_OK;
2478 			}
2479 		} else {
2480 			BAlert *alert = new BAlert("", kDeleteConfirmationStr,
2481 				"Cancel", "Delete", NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2482 				B_WARNING_ALERT);
2483 
2484 			alert->SetShortcut(0, B_ESCAPE);
2485 			alert->SetShortcut(1, 'd');
2486 
2487 			if (!alert->Go()) {
2488 				delete list;
2489 				return B_OK;
2490 			}
2491 		}
2492 	}
2493 
2494 	thread_id thread = find_thread(NULL);
2495 	if (gStatusWindow)
2496 		gStatusWindow->CreateStatusItem(thread, kDeleteState);
2497 
2498 	// calculate the sum total of all items on all volumes in trash
2499 	int32 totalItems = 0;
2500 	int64 totalSize = 0;
2501 
2502 	status_t err = CalcItemsAndSize(list, &totalItems, &totalSize);
2503 	if (err == B_OK) {
2504 		if (gStatusWindow)
2505 			gStatusWindow->InitStatusItem(thread, totalItems, totalItems);
2506 
2507 		int32 count = list->CountItems();
2508 		TrackerCopyLoopControl loopControl(thread);
2509 		for (int32 index = 0; index < count; index++) {
2510 			entry_ref ref(*list->ItemAt(index));
2511 			BEntry entry(&ref);
2512 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2513 			if (entry.IsDirectory())
2514 				err = FSDeleteFolder(&entry, &loopControl, true, true, true);
2515 			else
2516 				err = entry.Remove();
2517 		}
2518 
2519 		if (err != kTrashCanceled && err != kUserCanceled && err != B_OK)
2520 			(new BAlert("", "Error Deleting items", "OK", NULL, NULL,
2521 				B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2522 	}
2523 	if (gStatusWindow)
2524 		gStatusWindow->RemoveStatusItem(find_thread(NULL));
2525 
2526 	delete list;
2527 
2528 	return B_OK;
2529 }
2530 
2531 status_t
2532 FSRecursiveCreateFolder(BPath path)
2533 {
2534 	BEntry entry(path.Path());
2535 	if (entry.InitCheck() != B_OK) {
2536 		BPath parentPath;
2537 		status_t err = path.GetParent(&parentPath);
2538 		if (err != B_OK)
2539 			return err;
2540 
2541 		err = FSRecursiveCreateFolder(parentPath);
2542 		if (err != B_OK)
2543 			return err;
2544 	}
2545 
2546 	entry.SetTo(path.Path());
2547 	if (entry.Exists())
2548 		return B_FILE_EXISTS;
2549 	else {
2550 		char name[B_FILE_NAME_LENGTH];
2551 		BDirectory parent;
2552 
2553 		entry.GetParent(&parent);
2554 		entry.GetName(name);
2555 		parent.CreateDirectory(name, NULL);
2556 	}
2557 
2558 	return B_OK;
2559 }
2560 
2561 status_t
2562 _RestoreTask(BObjectList<entry_ref> *list)
2563 {
2564 	thread_id thread = find_thread(NULL);
2565 	if (gStatusWindow)
2566 		gStatusWindow->CreateStatusItem(thread, kRestoreFromTrashState);
2567 
2568 	// calculate the sum total of all items that will be restored
2569 	int32 totalItems = 0;
2570 	int64 totalSize = 0;
2571 
2572 	status_t err = CalcItemsAndSize(list, &totalItems, &totalSize);
2573 	if (err == B_OK) {
2574 		if (gStatusWindow)
2575 			gStatusWindow->InitStatusItem(thread, totalItems, totalItems);
2576 
2577 		int32 count = list->CountItems();
2578 		TrackerCopyLoopControl loopControl(thread);
2579 		for (int32 index = 0; index < count; index++) {
2580 			entry_ref ref(*list->ItemAt(index));
2581 			BEntry entry(&ref);
2582 			BPath originalPath;
2583 
2584 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2585 
2586 			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
2587 				continue;
2588 
2589 			BEntry originalEntry(originalPath.Path());
2590 			BPath parentPath;
2591 			err = originalPath.GetParent(&parentPath);
2592 			if (err != B_OK)
2593 				continue;
2594 			BEntry parentEntry(parentPath.Path());
2595 
2596 			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
2597 				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
2598 					originalEntry.SetTo(originalPath.Path());
2599 					if (entry.InitCheck() != B_OK)
2600 						continue;
2601 				}
2602 			}
2603 
2604 			if (!originalEntry.Exists()) {
2605 				BDirectory dir(parentPath.Path());
2606 				if (dir.InitCheck() == B_OK) {
2607 					char leafName[B_FILE_NAME_LENGTH];
2608 					originalEntry.GetName(leafName);
2609 					if (entry.MoveTo(&dir, leafName) == B_OK) {
2610 						BNode node(&entry);
2611 						if (node.InitCheck() == B_OK)
2612 							node.RemoveAttr(kAttrOriginalPath);
2613 					}
2614 				}
2615 			}
2616 
2617 			err = loopControl.CheckUserCanceled();
2618 			if (err != B_OK)
2619 				break;
2620 		}
2621 	}
2622 	if (gStatusWindow)
2623 		gStatusWindow->RemoveStatusItem(find_thread(NULL));
2624 
2625 	delete list;
2626 
2627 	return err;
2628 }
2629 
2630 void
2631 FSCreateTrashDirs()
2632 {
2633 	BVolume volume;
2634 	BVolumeRoster roster;
2635 
2636 	roster.Rewind();
2637 	while (roster.GetNextVolume(&volume) == B_OK) {
2638 		if (volume.IsReadOnly() || !volume.IsPersistent())
2639 			continue;
2640 
2641 		BPath path;
2642 		find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume);
2643 		find_directory(B_TRASH_DIRECTORY, &path, true, &volume);
2644 
2645 		BDirectory trashDir;
2646 		if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
2647 			size_t size;
2648 			const void* data = GetTrackerResources()->
2649 				LoadResource('ICON', kResTrashIcon, &size);
2650 			if (data) {
2651 				trashDir.WriteAttr(kAttrLargeIcon, 'ICON', 0,
2652 					data, size);
2653 			}
2654 			data = GetTrackerResources()->
2655 				LoadResource('MICN', kResTrashIcon, &size);
2656 			if (data) {
2657 				trashDir.WriteAttr(kAttrMiniIcon, 'MICN', 0,
2658 					data, size);
2659 			}
2660 			data = GetTrackerResources()->
2661 				LoadResource(B_VECTOR_ICON_TYPE, kResTrashIcon, &size);
2662 			if (data) {
2663 				trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0,
2664 					data, size);
2665 			}
2666 		}
2667 	}
2668 }
2669 
2670 
2671 status_t
2672 FSCreateNewFolder(const entry_ref *ref)
2673 {
2674 	node_ref node;
2675 	node.device = ref->device;
2676 	node.node = ref->directory;
2677 
2678 	BDirectory dir(&node);
2679 	status_t result = dir.InitCheck();
2680 	if (result != B_OK)
2681 		return result;
2682 
2683 	// ToDo: is that really necessary here?
2684 	BString name(ref->name);
2685 	FSMakeOriginalName(name, &dir, "-");
2686 
2687 	BDirectory newDir;
2688 	result = dir.CreateDirectory(name.String(), &newDir);
2689 	if (result != B_OK)
2690 		return result;
2691 
2692 	BNodeInfo nodeInfo(&newDir);
2693 	nodeInfo.SetType(B_DIR_MIMETYPE);
2694 
2695 	return result;
2696 }
2697 
2698 
2699 status_t
2700 FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef,
2701 	node_ref *newNode)
2702 {
2703 	BDirectory dir(dirNode);
2704 	status_t result = dir.InitCheck();
2705 	if (result == B_OK) {
2706 		char name[B_FILE_NAME_LENGTH];
2707 		strcpy(name, "New Folder");
2708 
2709 		int32 fnum = 1;
2710 		while (dir.Contains(name)) {
2711 			// if base name already exists then add a number
2712 			// ToDo:
2713 			// move this logic ot FSMakeOriginalName
2714 			if (++fnum > 9)
2715 				sprintf(name, "New Folder%ld", fnum);
2716 			else
2717 				sprintf(name, "New Folder %ld", fnum);
2718 		}
2719 
2720 		BDirectory newDir;
2721 		result = dir.CreateDirectory(name, &newDir);
2722 		if (result == B_OK) {
2723 			BEntry entry;
2724 			newDir.GetEntry(&entry);
2725 			entry.GetRef(newRef);
2726 			entry.GetNodeRef(newNode);
2727 
2728 			BNodeInfo nodeInfo(&newDir);
2729 			nodeInfo.SetType(B_DIR_MIMETYPE);
2730 
2731 			// add undo item
2732 			NewFolderUndo undo(*newRef);
2733 			return B_OK;
2734 		}
2735 	}
2736 
2737 	(new BAlert("", "Sorry, could not create a new folder.", "Cancel", 0, 0,
2738 		B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2739 	return result;
2740 }
2741 
2742 
2743 ReadAttrResult
2744 ReadAttr(const BNode *node, const char *hostAttrName, const char *foreignAttrName,
2745 	type_code type, off_t offset, void *buffer, size_t length,
2746 	void (*swapFunc)(void *), bool isForeign)
2747 {
2748 	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, length) == (ssize_t)length)
2749 		return kReadAttrNativeOK;
2750 
2751 	// PRINT(("trying %s\n", foreignAttrName));
2752 	// try the other endianness
2753 	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) != (ssize_t)length)
2754 		return kReadAttrFailed;
2755 
2756 	// PRINT(("got %s\n", foreignAttrName));
2757 	if (!swapFunc)
2758 		return kReadAttrForeignOK;
2759 
2760 	(swapFunc)(buffer);
2761 		// run the endian swapper
2762 
2763 	return kReadAttrForeignOK;
2764 }
2765 
2766 
2767 ReadAttrResult
2768 GetAttrInfo(const BNode *node, const char *hostAttrName, const char *foreignAttrName,
2769 	type_code *type, size_t *size)
2770 {
2771 	attr_info info;
2772 
2773 	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
2774 		if (type)
2775 			*type = info.type;
2776 		if (size)
2777 			*size = (size_t)info.size;
2778 
2779 		return kReadAttrNativeOK;
2780 	}
2781 
2782 	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
2783 		if (type)
2784 			*type = info.type;
2785 		if (size)
2786 			*size = (size_t)info.size;
2787 
2788 		return kReadAttrForeignOK;
2789 	}
2790 	return kReadAttrFailed;
2791 }
2792 
2793 // launching code
2794 
2795 static status_t
2796 TrackerOpenWith(const BMessage *refs)
2797 {
2798 	BMessage clone(*refs);
2799 	ASSERT(dynamic_cast<TTracker *>(be_app));
2800 	ASSERT(clone.what);
2801 	clone.AddInt32("launchUsingSelector", 0);
2802 	// runs the Open With window
2803 	be_app->PostMessage(&clone);
2804 
2805 	return B_OK;
2806 }
2807 
2808 static void
2809 AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on),
2810 	const entry_ref *entry, const BMessage *message, bool on)
2811 {
2812 	Thread::Launch(NewFunctionObject(func, entry, message, on),
2813 		B_NORMAL_PRIORITY, "LaunchTask");
2814 }
2815 
2816 static bool
2817 SniffIfGeneric(const entry_ref *ref)
2818 {
2819 	BNode node(ref);
2820 	char type[B_MIME_TYPE_LENGTH];
2821 	BNodeInfo info(&node);
2822 	if (info.GetType(type) == B_OK && strcasecmp(type, B_FILE_MIME_TYPE) != 0)
2823 		// already has a type and it's not octet stream
2824 		return false;
2825 
2826 	BPath path(ref);
2827 	if (path.Path()) {
2828 		// force a mimeset
2829 		node.RemoveAttr(kAttrMIMEType);
2830 		update_mime_info(path.Path(), 0, 1, 1);
2831 	}
2832 
2833 	return true;
2834 }
2835 
2836 static void
2837 SniffIfGeneric(const BMessage *refs)
2838 {
2839 	entry_ref ref;
2840 	for (int32 index = 0; ; index++) {
2841 		if (refs->FindRef("refs", index, &ref) != B_OK)
2842 			break;
2843 		SniffIfGeneric(&ref);
2844 	}
2845 }
2846 
2847 static void
2848 _TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs, bool openWithOK)
2849 {
2850 	team_id team;
2851 
2852 	status_t error = B_ERROR;
2853 	BString alertString;
2854 
2855 	for (int32 mimesetIt = 0; ; mimesetIt++) {
2856 		error = be_roster->Launch(appRef, refs, &team);
2857 		if (error == B_ALREADY_RUNNING)
2858 			// app already running, not really an error
2859 			error = B_OK;
2860 
2861 		if (error == B_OK)
2862 			break;
2863 
2864 		if (mimesetIt > 0)
2865 			break;
2866 
2867 		// failed to open, try mimesetting the refs and launching again
2868 		SniffIfGeneric(refs);
2869 	}
2870 
2871 	if (error == B_OK) {
2872 		// close possible parent window, if specified
2873 		const node_ref *nodeToClose = 0;
2874 		int32 numBytes;
2875 		refs->FindData("nodeRefsToClose", B_RAW_TYPE, (const void **)&nodeToClose, &numBytes);
2876 		if (nodeToClose)
2877 			dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose);
2878 	} else {
2879 		alertString << "Could not open \"" << appRef->name << "\" (" << strerror(error) << "). ";
2880 		if (refs && openWithOK) {
2881 			alertString << kFindAlternativeStr;
2882 			if ((new BAlert("", alertString.String(), "Cancel", "Find", 0,
2883 					B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1)
2884 				error = TrackerOpenWith(refs);
2885 		} else
2886 			(new BAlert("", alertString.String(), "Cancel", 0, 0,
2887 				B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2888 	}
2889 }
2890 
2891 extern "C" char** environ;
2892 extern "C"
2893 #if !B_BEOS_VERSION_DANO
2894 _IMPEXP_ROOT
2895 #endif
2896 status_t _kload_image_etc_(int argc, char **argv, char **envp,
2897 	char *buf, int bufsize);
2898 
2899 
2900 static status_t
2901 LoaderErrorDetails(const entry_ref *app, BString &details)
2902 {
2903 	BPath path;
2904 	BEntry appEntry(app, true);
2905 	status_t result = appEntry.GetPath(&path);
2906 
2907 	if (result != B_OK)
2908 		return result;
2909 
2910 	char *argv[2] = { const_cast<char *>(path.Path()), 0};
2911 
2912 #ifdef __HAIKU__
2913 	// ToDo: do this correctly!
2914 	result = load_image(1, (const char **)argv, (const char **)environ);
2915 	details.SetTo("ToDo: this is missing from Haiku");
2916 #else
2917 	result = _kload_image_etc_(1, argv, environ, details.LockBuffer(1024), 1024);
2918 	details.UnlockBuffer();
2919 #endif
2920 	return B_OK;
2921 }
2922 
2923 
2924 static void
2925 _TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs,
2926 	bool openWithOK)
2927 {
2928 	BMessage copyOfRefs(*refs);
2929 
2930 	entry_ref documentRef;
2931 	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK)
2932 		// nothing to launch, we are done
2933 		return;
2934 
2935 	status_t error = B_ERROR;
2936 	entry_ref app;
2937 	BMessage *refsToPass = NULL;
2938 	BString alertString;
2939 	const char *alternative = 0;
2940 
2941 	for (int32 mimesetIt = 0; ; mimesetIt++) {
2942 		alertString = "";
2943 		error = be_roster->FindApp(&documentRef, &app);
2944 
2945 		if (error != B_OK && mimesetIt == 0) {
2946 			SniffIfGeneric(&copyOfRefs);
2947 			continue;
2948 		}
2949 
2950 		if (error != B_OK) {
2951 			alertString << "Could not find an application to open \"" << documentRef.name
2952 				<< "\" (" << strerror(error) << "). ";
2953 			if (openWithOK)
2954 				alternative = kFindApplicationStr;
2955 
2956 			break;
2957 		} else {
2958 			BEntry appEntry(&app, true);
2959 			for (int32 index = 0;;) {
2960 				// remove the app itself from the refs received so we don't try
2961 				// to open ourselves
2962 				entry_ref ref;
2963 				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
2964 					break;
2965 
2966 				// deal with symlinks properly
2967 				BEntry documentEntry(&ref, true);
2968 				if (appEntry == documentEntry) {
2969 					PRINT(("stripping %s, app %s \n", ref.name, app.name));
2970 					copyOfRefs.RemoveData("refs", index);
2971 				} else {
2972 					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
2973 					index++;
2974 				}
2975 			}
2976 
2977 			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
2978 			team_id team;
2979 			error = be_roster->Launch(&app, refsToPass, &team);
2980 			if (error == B_ALREADY_RUNNING)
2981 				// app already running, not really an error
2982 				error = B_OK;
2983 			if (error == B_OK || mimesetIt != 0)
2984 				break;
2985 
2986 			SniffIfGeneric(&copyOfRefs);
2987 		}
2988 	}
2989 
2990 	if (error != B_OK && alertString.Length() == 0) {
2991 		BString loaderErrorString;
2992 		bool openedDocuments = true;
2993 
2994 		if (!refsToPass) {
2995 			// we just double clicked the app itself, do not offer to
2996 			// find a handling app
2997 			openWithOK = false;
2998 			openedDocuments = false;
2999 		}
3000 
3001 		if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3002 			alertString << "Could not open \"" << app.name
3003 				<< "\". The file is mistakenly marked as executable. ";
3004 
3005 			if (!openWithOK) {
3006 				// offer the possibility to change the permissions
3007 
3008 				alertString << "\nShould this be fixed?";
3009 				if ((new BAlert("", alertString.String(), "Cancel", "Proceed", 0,
3010 						B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) {
3011 					BEntry entry(&documentRef);
3012 					mode_t permissions;
3013 
3014 					error = entry.GetPermissions(&permissions);
3015 					if (error == B_OK)
3016 						error = entry.SetPermissions(permissions & ~(S_IXUSR | S_IXGRP | S_IXOTH));
3017 					if (error == B_OK) {
3018 						// we updated the permissions, so let's try again
3019 						_TrackerLaunchDocuments(NULL, refs, false);
3020 						return;
3021 					} else {
3022 						alertString = "Could not update permissions of file \"";
3023 						alertString << app.name << "\". " << strerror(error);
3024 					}
3025 				} else
3026 					return;
3027 			}
3028 
3029 			alternative = kFindApplicationStr;
3030 		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3031 			alertString << "Could not open \"" << documentRef.name
3032 				<< "\" because application \"" << app.name << "\" is in the trash. ";
3033 			alternative = kFindAlternativeStr;
3034 		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3035 			alertString << "Could not open \"" << documentRef.name << "\" "
3036 				<< "(" << strerror(error) << "). ";
3037 			alternative = kFindAlternativeStr;
3038 		} else if (error == B_MISSING_SYMBOL
3039 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3040 			alertString << "Could not open \"" << documentRef.name << "\" ";
3041 			if (openedDocuments)
3042 				alertString << "with application \"" << app.name << "\" ";
3043 			alertString << "(Missing symbol: " << loaderErrorString << "). \n";
3044 			alternative = kFindAlternativeStr;
3045 		} else if (error == B_MISSING_LIBRARY
3046 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3047 			alertString << "Could not open \"" << documentRef.name << "\" ";
3048 			if (openedDocuments)
3049 				alertString << "with application \"" << app.name << "\" ";
3050 			alertString << "(Missing library: " << loaderErrorString << "). \n";
3051 			alternative = kFindAlternativeStr;
3052 		} else {
3053 			alertString << "Could not open \"" << documentRef.name
3054 				<< "\" with application \"" << app.name << "\" (" << strerror(error) << "). ";
3055 			alternative = kFindAlternativeStr;
3056 		}
3057 	}
3058 
3059 	if (error != B_OK) {
3060 		if (openWithOK) {
3061 			ASSERT(alternative);
3062 			alertString << alternative;
3063 			if ((new BAlert("", alertString.String(), "Cancel", "Find", 0,
3064 					B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1)
3065 				error = TrackerOpenWith(refs);
3066 		} else
3067 			(new BAlert("", alertString.String(), "Cancel", 0, 0,
3068 					B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
3069 	}
3070 }
3071 
3072 // the following three calls don't return any reasonable error codes,
3073 // should fix that, making them void
3074 
3075 status_t
3076 TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async, bool openWithOK)
3077 {
3078 	if (!async)
3079 		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3080 	else
3081 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, openWithOK);
3082 
3083 	return B_OK;
3084 }
3085 
3086 status_t
3087 TrackerLaunch(const entry_ref *appRef, bool async)
3088 {
3089 	if (!async)
3090 		_TrackerLaunchAppWithDocuments(appRef, 0, false);
3091 	else
3092 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3093 
3094 	return B_OK;
3095 }
3096 
3097 status_t
3098 TrackerLaunch(const BMessage *refs, bool async, bool openWithOK)
3099 {
3100 	if (!async)
3101 		_TrackerLaunchDocuments(0, refs, openWithOK);
3102 	else
3103 		AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK);
3104 
3105 	return B_OK;
3106 }
3107 
3108 status_t
3109 LaunchBrokenLink(const char *signature, const BMessage *refs)
3110 {
3111 	// This call is to support a hacky workaround for double-clicking
3112 	// broken refs for cifs
3113 	be_roster->Launch(signature, const_cast<BMessage *>(refs));
3114 	return B_OK;
3115 }
3116 
3117 // external launch calls; need to be robust, work if Tracker is not running
3118 
3119 #if !B_BEOS_VERSION_DANO
3120 _IMPEXP_TRACKER
3121 #endif
3122 status_t
3123 FSLaunchItem(const entry_ref *application, const BMessage *refsReceived,
3124 	bool async, bool openWithOK)
3125 {
3126 	return TrackerLaunch(application, refsReceived, async, openWithOK);
3127 }
3128 
3129 
3130 #if !B_BEOS_VERSION_DANO
3131 _IMPEXP_TRACKER
3132 #endif
3133 status_t
3134 FSOpenWith(BMessage *listOfRefs)
3135 {
3136 	status_t result = B_ERROR;
3137 	listOfRefs->what = B_REFS_RECEIVED;
3138 
3139 	if (dynamic_cast<TTracker *>(be_app))
3140 		result = TrackerOpenWith(listOfRefs);
3141 	else
3142 		ASSERT(!"not yet implemented");
3143 
3144 	return result;
3145 }
3146 
3147 // legacy calls, need for compatibility
3148 
3149 void
3150 FSOpenWithDocuments(const entry_ref *executable, BMessage *documents)
3151 {
3152 	TrackerLaunch(executable, documents, true);
3153 	delete documents;
3154 }
3155 
3156 status_t
3157 FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs)
3158 {
3159 	BMessage temp(B_REFS_RECEIVED);
3160 	if (!listOfRefs) {
3161 		ASSERT(ref);
3162 		temp.AddRef("refs", ref);
3163 		listOfRefs = &temp;
3164 	}
3165 	FSOpenWith(listOfRefs);
3166 	return B_OK;
3167 }
3168 
3169 status_t
3170 FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async)
3171 {
3172 	if (message)
3173 		message->what = B_REFS_RECEIVED;
3174 
3175 	status_t result = TrackerLaunch(ref, message, async, true);
3176 	delete message;
3177 	return result;
3178 }
3179 
3180 
3181 void
3182 FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace)
3183 {
3184 	FSLaunchItem(ref, message, workspace, true);
3185 }
3186 
3187 // Get the original path of an entry in the trash
3188 status_t
3189 FSGetOriginalPath(BEntry *entry, BPath *result)
3190 {
3191 	status_t err;
3192 	entry_ref ref;
3193 	err = entry->GetRef(&ref);
3194 	if (err != B_OK)
3195 		return err;
3196 
3197 	// Only call the routine for entries in the trash
3198 	if (!FSInTrashDir(&ref))
3199 		return B_ERROR;
3200 
3201 	BNode node(entry);
3202 	BString originalPath;
3203 	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3204 		// We're in luck, the entry has the original path in an attribute
3205 		err = result->SetTo(originalPath.String());
3206 		return err;
3207 	}
3208 
3209 	// Iterate the parent directories to find one with
3210 	// the original path attribute
3211 	BEntry parent(*entry);
3212 	err = parent.InitCheck();
3213 	if (err != B_OK)
3214 		return err;
3215 
3216 	// walk up the directory structure until we find a node
3217 	// with original path attribute
3218 	do {
3219 		// move to the parent of this node
3220 		err = parent.GetParent(&parent);
3221 		if (err != B_OK)
3222 			return err;
3223 
3224 		// return if we are at the root of the trash
3225 		if (FSIsTrashDir(&parent))
3226 			return B_ENTRY_NOT_FOUND;
3227 
3228 		// get the parent as a node
3229 		err = node.SetTo(&parent);
3230 		if (err != B_OK)
3231 			return err;
3232 	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3233 
3234 	// Found the attribute, figure out there this file
3235 	// used to live, based on the successfully-read attribute
3236 	err = result->SetTo(originalPath.String());
3237 	if (err != B_OK)
3238 		return err;
3239 
3240 	BPath path, pathParent;
3241 	err = parent.GetPath(&pathParent);
3242 	if (err != B_OK)
3243 		return err;
3244 	err = entry->GetPath(&path);
3245 	if (err != B_OK)
3246 		return err;
3247 	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3248 		// compute the new path by appending the offset of
3249 		// the item we are locating, to the original path
3250 		// of the parent
3251 	return B_OK;
3252 }
3253 
3254 directory_which
3255 WellKnowEntryList::Match(const node_ref *node)
3256 {
3257 	const WellKnownEntry *result = MatchEntry(node);
3258 	if (result)
3259 		return result->which;
3260 
3261 	return (directory_which)-1;
3262 }
3263 
3264 const WellKnowEntryList::WellKnownEntry *
3265 WellKnowEntryList::MatchEntry(const node_ref *node)
3266 {
3267 	if (!self)
3268 		self = new WellKnowEntryList();
3269 
3270 	return self->MatchEntryCommon(node);
3271 }
3272 
3273 const WellKnowEntryList::WellKnownEntry *
3274 WellKnowEntryList::MatchEntryCommon(const node_ref *node)
3275 {
3276 	uint32 count = entries.size();
3277 	for (uint32 index = 0; index < count; index++)
3278 		if (*node == entries[index].node)
3279 			return &entries[index];
3280 
3281 	return NULL;
3282 }
3283 
3284 
3285 void
3286 WellKnowEntryList::Quit()
3287 {
3288 	delete self;
3289 	self = NULL;
3290 }
3291 
3292 void
3293 WellKnowEntryList::AddOne(directory_which which, const char *name)
3294 {
3295 	BPath path;
3296 	if (find_directory(which, &path, true) != B_OK)
3297 		return;
3298 
3299 	BEntry entry(path.Path());
3300 	node_ref node;
3301 	if (entry.GetNodeRef(&node) != B_OK)
3302 		return;
3303 
3304 	entries.push_back(WellKnownEntry(&node, which, name));
3305 }
3306 
3307 void
3308 WellKnowEntryList::AddOne(directory_which which, directory_which base,
3309 	const char *extra, const char *name)
3310 {
3311 	BPath path;
3312 	if (find_directory(base, &path, true) != B_OK)
3313 		return;
3314 
3315 	path.Append(extra);
3316 	BEntry entry(path.Path());
3317 	node_ref node;
3318 	if (entry.GetNodeRef(&node) != B_OK)
3319 		return;
3320 
3321 	entries.push_back(WellKnownEntry(&node, which, name));
3322 }
3323 
3324 void
3325 WellKnowEntryList::AddOne(directory_which which, const char *path, const char *name)
3326 {
3327 	BEntry entry(path);
3328 	node_ref node;
3329 	if (entry.GetNodeRef(&node) != B_OK)
3330 		return;
3331 
3332 	entries.push_back(WellKnownEntry(&node, which, name));
3333 }
3334 
3335 
3336 WellKnowEntryList::WellKnowEntryList()
3337 {
3338 	AddOne(B_BEOS_DIRECTORY, "beos");
3339 	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
3340 	AddOne(B_USER_DIRECTORY, "home");
3341 	AddOne(B_BEOS_SYSTEM_DIRECTORY, "system");
3342 
3343 	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
3344 	AddOne(B_COMMON_FONTS_DIRECTORY, "fonts");
3345 	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
3346 
3347 	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
3348 	AddOne(B_APPS_DIRECTORY, "apps");
3349 	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, B_USER_DESKBAR_DIRECTORY,
3350 		"Applications", "apps");
3351 
3352 	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
3353 	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
3354 	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, B_USER_DESKBAR_DIRECTORY,
3355 		"Preferences", "preferences");
3356 
3357 	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", "mail");
3358 
3359 	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, "queries", "queries");
3360 
3361 
3362 
3363 	AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop");
3364 	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, B_USER_DESKBAR_DIRECTORY,
3365 		"Development", "develop");
3366 
3367 	AddOne(B_USER_CONFIG_DIRECTORY, "config");
3368 
3369 	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, "people", "people");
3370 
3371 	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, "Downloads",
3372 		"Downloads");
3373 }
3374 
3375 WellKnowEntryList *WellKnowEntryList::self = NULL;
3376 
3377 }
3378