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