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