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