xref: /haiku/src/kits/tracker/FSUtils.cpp (revision 37fedaf8494b34aad811abcc49e79aa32943f880)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30 trademarks of Be Incorporated in the United States and other countries. Other
31 brand product names are registered trademarks or trademarks of their
32 respective holders. All rights reserved.
33 */
34 
35 // Tracker file system calls.
36 
37 // APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup -- in
38 // other words, you will find a lot of ugly cruft in here
39 
40 // ToDo:
41 // Move most of preflight error checks to the Model level and only keep those
42 // that have to do with size, reading/writing and name collisions.
43 // Get rid of all the BList based APIs, use BObjectLists.
44 // Clean up the error handling, push most of the user interaction out of the
45 // low level FS calls.
46 
47 
48 #include <ctype.h>
49 #include <errno.h>
50 #include <string.h>
51 #include <unistd.h>
52 
53 #include <Alert.h>
54 #include <Application.h>
55 #include <Catalog.h>
56 #include <Debug.h>
57 #include <Directory.h>
58 #include <Entry.h>
59 #include <FindDirectory.h>
60 #include <Locale.h>
61 #include <NodeInfo.h>
62 #include <Path.h>
63 #include <Roster.h>
64 #include <Screen.h>
65 #include <String.h>
66 #include <SymLink.h>
67 #include <Volume.h>
68 #include <VolumeRoster.h>
69 
70 #include <fs_attr.h>
71 #include <fs_info.h>
72 #include <sys/utsname.h>
73 
74 #include <AutoLocker.h>
75 #include <libroot/libroot_private.h>
76 #include <system/syscalls.h>
77 
78 #include "Attributes.h"
79 #include "Bitmaps.h"
80 #include "Commands.h"
81 #include "FSUndoRedo.h"
82 #include "FSUtils.h"
83 #include "InfoWindow.h"
84 #include "MimeTypes.h"
85 #include "Model.h"
86 #include "OverrideAlert.h"
87 #include "StatusWindow.h"
88 #include "Thread.h"
89 #include "Tracker.h"
90 #include "TrackerSettings.h"
91 #include "Utilities.h"
92 #include "VirtualDirectoryManager.h"
93 
94 
95 enum {
96 	kUserCanceled = B_ERRORS_END + 1,
97 	kCopyCanceled = kUserCanceled,
98 	kTrashCanceled
99 };
100 
101 enum ConflictCheckResult {
102 	kCanceled = kUserCanceled,
103 	kPrompt,
104 	kReplace,
105 	kReplaceAll,
106 	kNoConflicts
107 };
108 
109 
110 namespace BPrivate {
111 
112 #undef B_TRANSLATION_CONTEXT
113 #define B_TRANSLATION_CONTEXT "FSUtils"
114 
115 static status_t FSDeleteFolder(BEntry*, CopyLoopControl*, bool updateStatus,
116 	bool deleteTopDir = true, bool upateFileNameInStatus = false);
117 static status_t MoveEntryToTrash(BEntry*, BPoint*, Undo &undo);
118 static void LowLevelCopy(BEntry*, StatStruct*, BDirectory*, char* destName,
119 	CopyLoopControl*, BPoint*);
120 status_t DuplicateTask(BObjectList<entry_ref>* srcList);
121 static status_t MoveTask(BObjectList<entry_ref>*, BEntry*, BList*, uint32);
122 static status_t _DeleteTask(BObjectList<entry_ref>*, bool);
123 static status_t _RestoreTask(BObjectList<entry_ref>*);
124 status_t CalcItemsAndSize(CopyLoopControl* loopControl,
125 	BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
126 	off_t* totalSize);
127 status_t MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc,
128 	uint32 moveMode, const char* newName, Undo &undo,
129 	CopyLoopControl* loopControl);
130 ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref>* srcList,
131 	const BDirectory* destDir, int32* collisionCount, uint32 moveMode);
132 status_t CheckName(uint32 moveMode, const BEntry* srcEntry,
133 	const BDirectory* destDir, bool multipleCollisions,
134 	ConflictCheckResult &);
135 void CopyAttributes(CopyLoopControl* control, BNode* srcNode,
136 	BNode* destNode, void* buffer, size_t bufsize);
137 void CopyPoseLocation(BNode* src, BNode* dest);
138 bool DirectoryMatchesOrContains(const BEntry*, directory_which);
139 bool DirectoryMatchesOrContains(const BEntry*, const char* additionalPath,
140 	directory_which);
141 bool DirectoryMatches(const BEntry*, directory_which);
142 bool DirectoryMatches(const BEntry*, const char* additionalPath,
143 	directory_which);
144 
145 status_t empty_trash(void*);
146 
147 
148 static const char* kDeleteConfirmationStr =
149 	B_TRANSLATE_MARK("Are you sure you want to delete the "
150 	"selected item(s)? This operation cannot be reverted.");
151 
152 static const char* kReplaceStr =
153 	B_TRANSLATE_MARK("You are trying to replace the item:\n"
154 	"\t%name%dest\n"
155 	"with:\n"
156 	"\t%name%src\n\n"
157 	"Would you like to replace it with the one you are %movemode?");
158 
159 static const char* kDirectoryReplaceStr =
160 	B_TRANSLATE_MARK("An item named \"%name\" already exists in "
161 	"this folder, and may contain\nitems with the same names. Would you like "
162 	"to replace them with those contained in the folder you are %verb?");
163 
164 static const char* kSymLinkReplaceStr =
165 	B_TRANSLATE_MARK("An item named \"%name\" already exists in this "
166 	"folder. Would you like to replace it with the symbolic link you are "
167 	"creating?");
168 
169 static const char* kNoFreeSpace =
170 	B_TRANSLATE_MARK("Sorry, there is not enough free space on the "
171 	"destination volume to copy the selection.");
172 
173 static const char* kFileErrorString =
174 	B_TRANSLATE_MARK("Error copying file \"%name\":\n\t%error\n\n"
175 	"Would you like to continue?");
176 
177 static const char* kFolderErrorString =
178 	B_TRANSLATE_MARK("Error copying folder \"%name\":\n\t%error\n\n"
179 	"Would you like to continue?");
180 
181 static const char* kFileDeleteErrorString =
182 	B_TRANSLATE_MARK("There was an error deleting \"%name\""
183 	":\n\t%error");
184 
185 static const char* kReplaceManyStr =
186 	B_TRANSLATE_MARK("Some items already exist in this folder with "
187 	"the same names as the items you are %verb.\n \nWould you like to "
188 	"replace them with the ones you are %verb or be prompted for each "
189 	"one?");
190 
191 static const char* kFindAlternativeStr =
192 	B_TRANSLATE_MARK("Would you like to find some other suitable "
193 	"application?");
194 
195 static const char* kFindApplicationStr =
196 	B_TRANSLATE_MARK("Would you like to find a suitable application "
197 	"to open the file?");
198 
199 
200 // Skip these attributes when copying in Tracker
201 const char* kSkipAttributes[] = {
202 	kAttrPoseInfo,
203 	NULL
204 };
205 
206 
207 // #pragma mark - CopyLoopControl
208 
209 
210 CopyLoopControl::~CopyLoopControl()
211 {
212 }
213 
214 
215 void
216 CopyLoopControl::Init(uint32 jobKind)
217 {
218 }
219 
220 
221 void
222 CopyLoopControl::Init(int32 totalItems, off_t totalSize,
223 	const entry_ref* destDir, bool showCount)
224 {
225 }
226 
227 
228 bool
229 CopyLoopControl::FileError(const char* message, const char* name,
230 	status_t error, bool allowContinue)
231 {
232 	return false;
233 }
234 
235 
236 void
237 CopyLoopControl::UpdateStatus(const char* name, const entry_ref& ref,
238 	int32 count, bool optional)
239 {
240 }
241 
242 
243 bool
244 CopyLoopControl::CheckUserCanceled()
245 {
246 	return false;
247 }
248 
249 
250 CopyLoopControl::OverwriteMode
251 CopyLoopControl::OverwriteOnConflict(const BEntry* srcEntry,
252 	const char* destName, const BDirectory* destDir, bool srcIsDir,
253 	bool dstIsDir)
254 {
255 	return kReplace;
256 }
257 
258 
259 bool
260 CopyLoopControl::SkipEntry(const BEntry*, bool)
261 {
262 	// Tracker makes no exceptions
263 	return false;
264 }
265 
266 
267 void
268 CopyLoopControl::ChecksumChunk(const char*, size_t)
269 {
270 }
271 
272 
273 bool
274 CopyLoopControl::ChecksumFile(const entry_ref*)
275 {
276 	return true;
277 }
278 
279 
280 bool
281 CopyLoopControl::SkipAttribute(const char*)
282 {
283 	return false;
284 }
285 
286 
287 bool
288 CopyLoopControl::PreserveAttribute(const char*)
289 {
290 	return false;
291 }
292 
293 
294 // #pragma mark - TrackerCopyLoopControl
295 
296 
297 TrackerCopyLoopControl::TrackerCopyLoopControl()
298 	:
299 	fThread(find_thread(NULL)),
300 	fSourceList(NULL)
301 {
302 }
303 
304 
305 TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind)
306 	:
307 	fThread(find_thread(NULL)),
308 	fSourceList(NULL)
309 {
310 	Init(jobKind);
311 }
312 
313 
314 TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems,
315 		off_t totalSize)
316 	:
317 	fThread(find_thread(NULL)),
318 	fSourceList(NULL)
319 {
320 	Init(totalItems, totalSize);
321 }
322 
323 
324 TrackerCopyLoopControl::~TrackerCopyLoopControl()
325 {
326 	if (gStatusWindow != NULL)
327 		gStatusWindow->RemoveStatusItem(fThread);
328 }
329 
330 
331 void
332 TrackerCopyLoopControl::Init(uint32 jobKind)
333 {
334 	if (gStatusWindow != NULL)
335 		gStatusWindow->CreateStatusItem(fThread, (StatusWindowState)jobKind);
336 }
337 
338 
339 void
340 TrackerCopyLoopControl::Init(int32 totalItems, off_t totalSize,
341 	const entry_ref* destDir, bool showCount)
342 {
343 	if (gStatusWindow != NULL) {
344 		gStatusWindow->InitStatusItem(fThread, totalItems, totalSize,
345 			destDir, showCount);
346 	}
347 }
348 
349 
350 bool
351 TrackerCopyLoopControl::FileError(const char* message, const char* name,
352 	status_t error, bool allowContinue)
353 {
354 	BString buffer(message);
355 	buffer.ReplaceFirst("%name", name);
356 	buffer.ReplaceFirst("%error", strerror(error));
357 
358 	if (allowContinue) {
359 		BAlert* alert = new BAlert("", buffer.String(),	B_TRANSLATE("Cancel"),
360 			B_TRANSLATE("OK"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
361 		alert->SetShortcut(0, B_ESCAPE);
362 		return alert->Go() != 0;
363 	}
364 
365 	BAlert* alert = new BAlert("", buffer.String(),	B_TRANSLATE("Cancel"), 0, 0,
366 		B_WIDTH_AS_USUAL, B_STOP_ALERT);
367 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
368 	alert->Go();
369 	return false;
370 }
371 
372 
373 void
374 TrackerCopyLoopControl::UpdateStatus(const char* name, const entry_ref&,
375 	int32 count, bool optional)
376 {
377 	if (gStatusWindow != NULL)
378 		gStatusWindow->UpdateStatus(fThread, name, count, optional);
379 }
380 
381 
382 bool
383 TrackerCopyLoopControl::CheckUserCanceled()
384 {
385 	if (gStatusWindow == NULL)
386 		return false;
387 
388 	if (gStatusWindow->CheckCanceledOrPaused(fThread))
389 		return true;
390 
391 	if (fSourceList != NULL) {
392 		// TODO: Check if the user dropped additional files onto this job.
393 //		printf("%p->CheckUserCanceled()\n", this);
394 	}
395 
396 	return false;
397 }
398 
399 
400 bool
401 TrackerCopyLoopControl::SkipAttribute(const char* attributeName)
402 {
403 	for (const char** skipAttribute = kSkipAttributes; *skipAttribute;
404 		skipAttribute++) {
405 		if (strcmp(*skipAttribute, attributeName) == 0)
406 			return true;
407 	}
408 
409 	return false;
410 }
411 
412 
413 void
414 TrackerCopyLoopControl::SetSourceList(EntryList* list)
415 {
416 	fSourceList = list;
417 }
418 
419 
420 // #pragma mark - the rest
421 
422 
423 static BNode*
424 GetWritableNode(BEntry* entry, StatStruct* statBuf = 0)
425 {
426 	// utility call that works around the problem with BNodes not being
427 	// universally writeable
428 	// BNodes created on files will fail to WriteAttr because they do not
429 	// have the right r/w permissions
430 
431 	StatStruct localStatbuf;
432 
433 	if (!statBuf) {
434 		statBuf = &localStatbuf;
435 		if (entry->GetStat(statBuf) != B_OK)
436 			return 0;
437 	}
438 
439 	if (S_ISREG(statBuf->st_mode))
440 		return new BFile(entry, O_RDWR);
441 
442 	return new BNode(entry);
443 }
444 
445 
446 bool
447 CheckDevicesEqual(const entry_ref* srcRef, const Model* targetModel)
448 {
449 	BDirectory destDir (targetModel->EntryRef());
450 	struct stat deststat;
451 	destDir.GetStat(&deststat);
452 
453 	return srcRef->device == deststat.st_dev;
454 }
455 
456 
457 status_t
458 FSSetPoseLocation(ino_t destDirInode, BNode* destNode, BPoint point)
459 {
460 	PoseInfo poseInfo;
461 	poseInfo.fInvisible = false;
462 	poseInfo.fInitedDirectory = destDirInode;
463 	poseInfo.fLocation = point;
464 
465 	status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
466 		&poseInfo, sizeof(poseInfo));
467 
468 	if (result == sizeof(poseInfo))
469 		return B_OK;
470 
471 	return result;
472 }
473 
474 
475 status_t
476 FSSetPoseLocation(BEntry* entry, BPoint point)
477 {
478 	BNode node(entry);
479 	status_t result = node.InitCheck();
480 	if (result != B_OK)
481 		return result;
482 
483 	BDirectory parent;
484 	result = entry->GetParent(&parent);
485 	if (result != B_OK)
486 		return result;
487 
488 	node_ref destNodeRef;
489 	result = parent.GetNodeRef(&destNodeRef);
490 	if (result != B_OK)
491 		return result;
492 
493 	return FSSetPoseLocation(destNodeRef.node, &node, point);
494 }
495 
496 
497 bool
498 FSGetPoseLocation(const BNode* node, BPoint* point)
499 {
500 	PoseInfo poseInfo;
501 	if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign,
502 		B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap)
503 		== kReadAttrFailed)
504 		return false;
505 
506 	if (poseInfo.fInitedDirectory == -1LL)
507 		return false;
508 
509 	*point = poseInfo.fLocation;
510 
511 	return true;
512 }
513 
514 
515 static void
516 SetUpPoseLocation(ino_t sourceParentIno, ino_t destParentIno,
517 	const BNode* sourceNode, BNode* destNode, BPoint* loc)
518 {
519 	BPoint point;
520 	if (!loc
521 		// we don't have a position yet
522 		&& sourceParentIno != destParentIno
523 		// we aren't  copying into the same directory
524 		&& FSGetPoseLocation(sourceNode, &point))
525 		// the original has a valid inited location
526 		loc = &point;
527 		// copy the originals location
528 
529 	if (loc && loc != (BPoint*)-1) {
530 		// loc of -1 is used when copying/moving into a window in list mode
531 		// where copying positions would not work
532 		// ToSo:
533 		// should push all this logic to upper levels
534 		FSSetPoseLocation(destParentIno, destNode, *loc);
535 	}
536 }
537 
538 
539 void
540 FSMoveToFolder(BObjectList<entry_ref>* srcList, BEntry* destEntry,
541 	uint32 moveMode, BList* pointList)
542 {
543 	if (srcList->IsEmpty()) {
544 		delete srcList;
545 		delete pointList;
546 		delete destEntry;
547 		return;
548 	}
549 
550 	LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
551 		destEntry, pointList, moveMode);
552 }
553 
554 
555 void
556 FSDelete(entry_ref* ref, bool async, bool confirm)
557 {
558 	BObjectList<entry_ref>* list = new BObjectList<entry_ref>(1, true);
559 	list->AddItem(ref);
560 	FSDeleteRefList(list, async, confirm);
561 }
562 
563 
564 void
565 FSDeleteRefList(BObjectList<entry_ref>* list, bool async, bool confirm)
566 {
567 	if (async) {
568 		LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list,
569 			confirm);
570 	} else
571 		_DeleteTask(list, confirm);
572 }
573 
574 
575 void
576 FSRestoreRefList(BObjectList<entry_ref>* list, bool async)
577 {
578 	if (async) {
579 		LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask,
580 			list);
581 	} else
582 		_RestoreTask(list);
583 }
584 
585 
586 void
587 FSMoveToTrash(BObjectList<entry_ref>* srcList, BList* pointList, bool async)
588 {
589 	if (srcList->IsEmpty()) {
590 		delete srcList;
591 		delete pointList;
592 		return;
593 	}
594 
595 	if (async)
596 		LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
597 			(BEntry*)0, pointList, kMoveSelectionTo);
598 	else
599 		MoveTask(srcList, 0, pointList, kMoveSelectionTo);
600 }
601 
602 
603 static bool
604 IsDisksWindowIcon(BEntry* entry)
605 {
606 	BPath path;
607 	if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
608 		return false;
609 
610 	return strcmp(path.Path(), "/") == 0;
611 }
612 
613 enum {
614 	kNotConfirmed,
615 	kConfirmedHomeMove,
616 	kConfirmedAll
617 };
618 
619 
620 bool
621 ConfirmChangeIfWellKnownDirectory(const BEntry* entry,
622 	const char* ifYouDoAction, const char* toDoAction,
623 	const char* toConfirmAction, bool dontAsk, int32* confirmedAlready)
624 {
625 	// Don't let the user casually move/change important files/folders
626 	//
627 	// This is a cheap replacement for having a real UID support turned
628 	// on and not running as root all the time
629 
630 	if (confirmedAlready && *confirmedAlready == kConfirmedAll)
631 		return true;
632 
633 	if (FSIsDeskDir(entry) || FSIsTrashDir(entry) || FSIsRootDir(entry))
634 		return false;
635 
636 	if (!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)
637 		&& !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY))
638 		// quick way out
639 		return true;
640 
641 	BString warning;
642 	bool requireOverride = true;
643 
644 	if (DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)) {
645 		warning.SetTo(
646 			B_TRANSLATE("If you %ifYouDoAction the system folder or its "
647 			"contents, you won't be able to boot %osName!\n\nAre you sure "
648 			"you want to do this?\n\nTo %toDoAction the system folder or its "
649 			"contents anyway, hold down the Shift key and click "
650 			"\"%toConfirmAction\"."));
651 	} else if (DirectoryMatches(entry, B_USER_DIRECTORY)) {
652 		warning .SetTo(
653 			B_TRANSLATE("If you %ifYouDoAction the home folder, %osName "
654 			"may not behave properly!\n\nAre you sure you want to do this?"
655 			"\n\nTo %toDoAction the home folder anyway, hold down the "
656 			"Shift key and click \"%toConfirmAction\"."));
657 	} else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY)
658 		|| DirectoryMatchesOrContains(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
659 
660 		if (DirectoryMatchesOrContains(entry, "beos_mime",
661 				B_USER_SETTINGS_DIRECTORY)
662 			|| DirectoryMatchesOrContains(entry, "beos_mime",
663 				B_SYSTEM_SETTINGS_DIRECTORY)) {
664 			warning.SetTo(
665 				B_TRANSLATE("If you %ifYouDoAction the mime settings, "
666 				"%osName may not behave properly!\n\nAre you sure you want "
667 				"to do this?"));
668 			requireOverride = false;
669 		} else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) {
670 			warning.SetTo(
671 				B_TRANSLATE("If you %ifYouDoAction the config folder, "
672 				"%osName may not behave properly!\n\nAre you sure you want "
673 				"to do this?"));
674 			requireOverride = false;
675 		} else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY)
676 			|| DirectoryMatches(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
677 			warning.SetTo(
678 				B_TRANSLATE("If you %ifYouDoAction the settings folder, "
679 				"%osName may not behave properly!\n\nAre you sure you want "
680 				"to do this?"));
681 			requireOverride = false;
682 		}
683 	}
684 
685 	if (!warning.Length())
686 		return true;
687 
688 	if (dontAsk)
689 		return false;
690 
691 	if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove
692 		&& !requireOverride)
693 		// we already warned about moving home this time around
694 		return true;
695 
696 	struct utsname name;
697 	if (uname(&name) == -1)
698 		warning.ReplaceFirst("%osName", "Haiku");
699 	else
700 		warning.ReplaceFirst("%osName", name.sysname);
701 
702 	warning.ReplaceFirst("%ifYouDoAction", ifYouDoAction);
703 	warning.ReplaceFirst("%toDoAction", toDoAction);
704 	warning.ReplaceFirst("%toConfirmAction", toConfirmAction);
705 
706 	BString buttonLabel(toConfirmAction);
707 
708 	OverrideAlert* alert = new OverrideAlert("", warning.String(),
709 		buttonLabel.String(), (requireOverride ? B_SHIFT_KEY : 0),
710 		B_TRANSLATE("Cancel"), 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
711 	alert->SetShortcut(1, B_ESCAPE);
712 	if (alert->Go() == 1) {
713 		if (confirmedAlready)
714 			*confirmedAlready = kNotConfirmed;
715 		return false;
716 	}
717 
718 	if (confirmedAlready) {
719 		if (!requireOverride)
720 			*confirmedAlready = kConfirmedHomeMove;
721 		else
722 			*confirmedAlready = kConfirmedAll;
723 	}
724 
725 	return true;
726 }
727 
728 
729 static status_t
730 InitCopy(CopyLoopControl* loopControl, uint32 moveMode,
731 	BObjectList<entry_ref>* srcList, BVolume* dstVol, BDirectory* destDir,
732 	entry_ref* destRef, bool preflightNameCheck, bool needSizeCalculation,
733 	int32* collisionCount, ConflictCheckResult* preflightResult)
734 {
735 	if (dstVol->IsReadOnly()) {
736 		BAlert* alert = new BAlert("",
737 			B_TRANSLATE("You can't move or copy items to read-only volumes."),
738 			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
739 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
740 		alert->Go();
741 		return B_ERROR;
742 	}
743 
744 	int32 numItems = srcList->CountItems();
745 	int32 askOnceOnly = kNotConfirmed;
746 	for (int32 index = 0; index < numItems; index++) {
747 		// we could check for this while iterating through items in each of
748 		// the copy loops, except it takes forever to call CalcItemsAndSize
749 		BEntry entry((entry_ref*)srcList->ItemAt(index));
750 		if (IsDisksWindowIcon(&entry)) {
751 			BString errorStr;
752 			if (moveMode == kCreateLink) {
753 				errorStr.SetTo(
754 					B_TRANSLATE("You cannot create a link to the root "
755 					"directory."));
756 			} else {
757 				errorStr.SetTo(
758 					B_TRANSLATE("You cannot copy or move the root "
759 					"directory."));
760 			}
761 
762 			BAlert* alert = new BAlert("", errorStr.String(),
763 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
764 				B_WARNING_ALERT);
765 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
766 			alert->Go();
767 			return B_ERROR;
768 		}
769 		if (moveMode == kMoveSelectionTo
770 			&& !ConfirmChangeIfWellKnownDirectory(&entry,
771 				B_TRANSLATE_COMMENT("move",
772 					"As in 'if you move this folder...' (en) "
773 					"'Wird dieser Ordner verschoben...' (de)"),
774 				B_TRANSLATE_COMMENT("move",
775 					"As in 'to move this folder...' (en) "
776 					"Um diesen Ordner zu verschieben...' (de)"),
777 				B_TRANSLATE_COMMENT("Move",
778 					"Button label, 'Move' (en), 'Verschieben' (de)"),
779 				false, &askOnceOnly)) {
780 			return B_ERROR;
781 		}
782 	}
783 
784 	if (preflightNameCheck) {
785 		ASSERT(collisionCount);
786 		ASSERT(preflightResult);
787 
788 		*preflightResult = kPrompt;
789 		*collisionCount = 0;
790 
791 		*preflightResult = PreFlightNameCheck(srcList, destDir,
792 			collisionCount, moveMode);
793 		if (*preflightResult == kCanceled) {
794 			// user canceled
795 			return B_ERROR;
796 		}
797 	}
798 
799 	// set up the status display
800 	switch (moveMode) {
801 		case kCopySelectionTo:
802 		case kDuplicateSelection:
803 		case kMoveSelectionTo:
804 			{
805 				loopControl->Init(moveMode == kMoveSelectionTo ? kMoveState
806 					: kCopyState);
807 
808 				int32 totalItems = 0;
809 				off_t totalSize = 0;
810 				if (needSizeCalculation) {
811 					if (CalcItemsAndSize(loopControl, srcList,
812 							dstVol->BlockSize(), &totalItems, &totalSize)
813 							!= B_OK) {
814 						return B_ERROR;
815 					}
816 
817 					// check for free space before starting copy
818 					if ((totalSize + (4* kKBSize)) >= dstVol->FreeBytes()) {
819 						BAlert* alert = new BAlert("",
820 							B_TRANSLATE_NOCOLLECT(kNoFreeSpace),
821 							B_TRANSLATE("Cancel"),
822 							0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
823 						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
824 						alert->Go();
825 						return B_ERROR;
826 					}
827 				}
828 
829 				loopControl->Init(totalItems, totalSize, destRef);
830 				break;
831 			}
832 
833 		case kCreateLink:
834 			if (numItems > 10) {
835 				// this will be fast, only put up status if lots of items
836 				// moved, links created
837 				loopControl->Init(kCreateLinkState);
838 				loopControl->Init(numItems, numItems, destRef);
839 			}
840 			break;
841 	}
842 	return B_OK;
843 }
844 
845 
846 // ToDo:
847 // get rid of this cruft
848 bool
849 delete_ref(void* ref)
850 {
851 	delete (entry_ref*)ref;
852 	return false;
853 }
854 
855 
856 bool
857 delete_point(void* point)
858 {
859 	delete (BPoint*)point;
860 	return false;
861 }
862 
863 
864 static status_t
865 MoveTask(BObjectList<entry_ref>* srcList, BEntry* destEntry, BList* pointList,
866 	uint32 moveMode)
867 {
868 	ASSERT(!srcList->IsEmpty());
869 
870 	// extract information from src, dest models
871 	// ## note that we're assuming all items come from the same volume
872 	// ## by looking only at FirstItem here which is not a good idea
873 	dev_t srcVolumeDevice = srcList->FirstItem()->device;
874 	dev_t destVolumeDevice = srcVolumeDevice;
875 
876 	StatStruct deststat;
877 	BVolume volume(srcVolumeDevice);
878 	entry_ref destRef;
879 
880 	bool destIsTrash = false;
881 	BDirectory destDir;
882 	BDirectory* destDirToCheck = NULL;
883 	bool needPreflightNameCheck = false;
884 	bool sourceIsReadOnly = volume.IsReadOnly();
885 	volume.Unset();
886 
887 	bool fromUndo = FSIsUndoMoveMode(moveMode);
888 	moveMode = FSMoveMode(moveMode);
889 
890 	// if we're not passed a destEntry then we are supposed to move to trash
891 	if (destEntry != NULL) {
892 		destEntry->GetRef(&destRef);
893 
894 		destDir.SetTo(destEntry);
895 		destDir.GetStat(&deststat);
896 		destDirToCheck = &destDir;
897 
898 		destVolumeDevice = deststat.st_dev;
899 		destIsTrash = FSIsTrashDir(destEntry);
900 		volume.SetTo(destVolumeDevice);
901 
902 		needPreflightNameCheck = true;
903 	} else if (moveMode == kDuplicateSelection) {
904 		BEntry entry;
905 		entry.SetTo(srcList->FirstItem());
906 		entry.GetParent(&destDir);
907 		volume.SetTo(srcVolumeDevice);
908 	} else {
909 		// move is to trash
910 		destIsTrash = true;
911 
912 		FSGetTrashDir(&destDir, srcVolumeDevice);
913 		volume.SetTo(srcVolumeDevice);
914 
915 		BEntry entry;
916 		destDir.GetEntry(&entry);
917 		destDirToCheck = &destDir;
918 
919 		entry.GetRef(&destRef);
920 	}
921 
922 	// change the move mode if needed
923 	if (moveMode == kCopySelectionTo && destIsTrash) {
924 		// cannot copy to trash
925 		moveMode = kMoveSelectionTo;
926 	}
927 
928 	if (moveMode == kMoveSelectionTo && sourceIsReadOnly)
929 		moveMode = kCopySelectionTo;
930 
931 	bool needSizeCalculation = true;
932 	if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice)
933 		|| destIsTrash) {
934 		needSizeCalculation = false;
935 	}
936 
937 	// we need the undo object later on, so we create it no matter
938 	// if we really need it or not (it's very lightweight)
939 	MoveCopyUndo undo(srcList, destDir, pointList, moveMode);
940 	if (fromUndo)
941 		undo.Remove();
942 
943 	TrackerCopyLoopControl loopControl;
944 
945 	ConflictCheckResult conflictCheckResult = kPrompt;
946 	int32 collisionCount = 0;
947 	// TODO: Status item is created in InitCopy(), but it would be kind of
948 	// neat to move all that into TrackerCopyLoopControl
949 	status_t result = InitCopy(&loopControl, moveMode, srcList,
950 		&volume, destDirToCheck, &destRef, needPreflightNameCheck,
951 		needSizeCalculation, &collisionCount, &conflictCheckResult);
952 
953 	loopControl.SetSourceList(srcList);
954 
955 	if (result == B_OK) {
956 		for (int32 i = 0; i < srcList->CountItems(); i++) {
957 			BPoint* loc = (BPoint*)-1;
958 				// a loc of -1 forces autoplacement, rather than copying the
959 				// position of the original node
960 				// TODO:
961 				// Clean this mess up!
962 				// What could be a cleaner design is to pass along some kind
963 				// "filter" object that post-processes poses, i.e. adds the
964 				// location or other stuff. It should not be a job of the
965 				// copy-engine.
966 
967 			entry_ref* srcRef = srcList->ItemAt(i);
968 
969 			if (moveMode == kDuplicateSelection) {
970 				BEntry entry(srcRef);
971 				entry.GetParent(&destDir);
972 				destDir.GetStat(&deststat);
973 				volume.SetTo(srcRef->device);
974 			}
975 
976 			// handle case where item is dropped into folder it already lives
977 			// in which could happen if dragging from a query window
978 			if (moveMode != kCreateLink
979 				&& moveMode != kCreateRelativeLink
980 				&& moveMode != kDuplicateSelection
981 				&& !destIsTrash
982 				&& (srcRef->device == destRef.device
983 					&& srcRef->directory == deststat.st_ino))
984 				continue;
985 
986 			if (loopControl.CheckUserCanceled())
987 				break;
988 
989 			BEntry sourceEntry(srcRef);
990 			if (sourceEntry.InitCheck() != B_OK) {
991 				BString error(B_TRANSLATE("Error moving \"%name\"."));
992 				error.ReplaceFirst("%name", srcRef->name);
993 				BAlert* alert = new BAlert("", error.String(),
994 					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
995 					B_WARNING_ALERT);
996 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
997 				alert->Go();
998 				break;
999 			}
1000 
1001 			// are we moving item to trash?
1002 			if (destIsTrash) {
1003 				if (pointList)
1004 					loc = (BPoint*)pointList->ItemAt(i);
1005 
1006 				result = MoveEntryToTrash(&sourceEntry, loc, undo);
1007 				if (result != B_OK) {
1008 					BString error(B_TRANSLATE("Error moving \"%name\" to Trash. "
1009 						"(%error)"));
1010 					error.ReplaceFirst("%name", srcRef->name);
1011 					error.ReplaceFirst("%error", strerror(result));
1012 					BAlert* alert = new BAlert("", error.String(),
1013 						B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1014 						B_WARNING_ALERT);
1015 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1016 					alert->Go();
1017 					break;
1018 				}
1019 				continue;
1020 			}
1021 
1022 			// resolve name collisions and hierarchy problems
1023 			if (CheckName(moveMode, &sourceEntry, &destDir,
1024 				collisionCount > 1, conflictCheckResult) != B_OK) {
1025 				// we will skip the current item, because we got a conflict
1026 				// and were asked to or because there was some conflict
1027 
1028 				// update the status because item got skipped and the status
1029 				// will not get updated by the move call
1030 				loopControl.UpdateStatus(srcRef->name, *srcRef, 1);
1031 
1032 				continue;
1033 			}
1034 
1035 			// get location to place this item
1036 			if (pointList && moveMode != kCopySelectionTo) {
1037 				loc = (BPoint*)pointList->ItemAt(i);
1038 
1039 				BNode* src_node = GetWritableNode(&sourceEntry);
1040 				if (src_node && src_node->InitCheck() == B_OK) {
1041 					PoseInfo poseInfo;
1042 					poseInfo.fInvisible = false;
1043 					poseInfo.fInitedDirectory = deststat.st_ino;
1044 					poseInfo.fLocation = *loc;
1045 					src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
1046 						&poseInfo, sizeof(poseInfo));
1047 				}
1048 				delete src_node;
1049 			}
1050 
1051 			if (pointList)
1052  				loc = (BPoint*)pointList->ItemAt(i);
1053 
1054 			result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL,
1055 				undo, &loopControl);
1056 			if (result != B_OK)
1057 				break;
1058 		}
1059 	}
1060 
1061 	// duplicates of srcList, destFolder were created - dispose them
1062 	delete srcList;
1063 	delete destEntry;
1064 
1065 	// delete file location list and all Points within
1066 	if (pointList != NULL) {
1067 		pointList->DoForEach(delete_point);
1068 		delete pointList;
1069 	}
1070 
1071 	return B_OK;
1072 }
1073 
1074 
1075 class FailWithAlert {
1076 	public:
1077 		static void FailOnError(status_t error, const char* string,
1078 			const char* name = NULL)
1079 		{
1080 			if (error != B_OK)
1081 				throw FailWithAlert(error, string, name);
1082 		}
1083 
1084 		FailWithAlert(status_t error, const char* string, const char* name)
1085 		:
1086 		fString(string),
1087 		fName(name),
1088 		fError(error)
1089 		{
1090 		}
1091 
1092 		const char* fString;
1093 		const char* fName;
1094 		status_t fError;
1095 };
1096 
1097 class MoveError {
1098 	public:
1099 		static void FailOnError(status_t error)
1100 		{
1101 			if (error != B_OK)
1102 				throw MoveError(error);
1103 		}
1104 
1105 		MoveError(status_t error)
1106 		:	fError(error)
1107 		{
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 
3345 static status_t
3346 LoaderErrorDetails(const entry_ref* app, BString &details)
3347 {
3348 	BPath path;
3349 	BEntry appEntry(app, true);
3350 
3351 	status_t result = appEntry.GetPath(&path);
3352 	if (result != B_OK)
3353 		return result;
3354 
3355 	char* argv[2] = { const_cast<char*>(path.Path()), 0};
3356 
3357 	port_id errorPort = create_port(1, "Tracker loader error");
3358 
3359 	// count environment variables
3360 	int32 envCount = 0;
3361 	while (environ[envCount] != NULL)
3362 		envCount++;
3363 
3364 	char** flatArgs = NULL;
3365 	size_t flatArgsSize;
3366 	result = __flatten_process_args((const char**)argv, 1,
3367 		environ, &envCount, argv[0], &flatArgs, &flatArgsSize);
3368 	if (result != B_OK)
3369 		return result;
3370 
3371 	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3372 		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3373 	if (result == B_OK) {
3374 		// we weren't supposed to be able to start the application...
3375 		return B_ERROR;
3376 	}
3377 
3378 	// read error message from port and construct details string
3379 
3380 	ssize_t bufferSize;
3381 
3382 	do {
3383 		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3384 	} while (bufferSize == B_INTERRUPTED);
3385 
3386 	if (bufferSize <= B_OK) {
3387 		delete_port(errorPort);
3388 		return bufferSize;
3389 	}
3390 
3391 	uint8* buffer = (uint8*)malloc(bufferSize);
3392 	if (buffer == NULL) {
3393 		delete_port(errorPort);
3394 		return B_NO_MEMORY;
3395 	}
3396 
3397 	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3398 		B_RELATIVE_TIMEOUT, 0);
3399 	delete_port(errorPort);
3400 
3401 	if (bufferSize < B_OK) {
3402 		free(buffer);
3403 		return bufferSize;
3404 	}
3405 
3406 	BMessage message;
3407 	result = message.Unflatten((const char*)buffer);
3408 	free(buffer);
3409 
3410 	if (result != B_OK)
3411 		return result;
3412 
3413 	int32 errorCode = B_ERROR;
3414 	result = message.FindInt32("error", &errorCode);
3415 	if (result != B_OK)
3416 		return result;
3417 
3418 	const char* detailName = NULL;
3419 	switch (errorCode) {
3420 		case B_MISSING_LIBRARY:
3421 			detailName = "missing library";
3422 			break;
3423 
3424 		case B_MISSING_SYMBOL:
3425 			detailName = "missing symbol";
3426 			break;
3427 	}
3428 
3429 	if (detailName == NULL)
3430 		return B_ERROR;
3431 
3432 	const char* detail;
3433 	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3434 			i++) {
3435 		if (i > 0)
3436 			details += ", ";
3437 		details += detail;
3438 	}
3439 
3440 	return B_OK;
3441 }
3442 
3443 
3444 static void
3445 _TrackerLaunchDocuments(const entry_ref* /*doNotUse*/, const BMessage* refs,
3446 	bool openWithOK)
3447 {
3448 	BMessage copyOfRefs(*refs);
3449 
3450 	entry_ref documentRef;
3451 	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) {
3452 		// nothing to launch, we are done
3453 		return;
3454 	}
3455 
3456 	status_t error = B_ERROR;
3457 	entry_ref app;
3458 	BMessage* refsToPass = NULL;
3459 	BString alertString;
3460 	const char* alternative = 0;
3461 
3462 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3463 		alertString = "";
3464 		error = be_roster->FindApp(&documentRef, &app);
3465 
3466 		if (error != B_OK && mimesetIt == 0) {
3467 			SniffIfGeneric(&copyOfRefs);
3468 			continue;
3469 		}
3470 
3471 		if (error != B_OK) {
3472 			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3473 				"open \"%name\" (%error). "));
3474 			alertString.ReplaceFirst("%name", documentRef.name);
3475 			alertString.ReplaceFirst("%error", strerror(error));
3476 			if (openWithOK)
3477 				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3478 
3479 			break;
3480 		} else {
3481 			BEntry appEntry(&app, true);
3482 			for (int32 index = 0;;) {
3483 				// remove the app itself from the refs received so we don't
3484 				// try to open ourselves
3485 				entry_ref ref;
3486 				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3487 					break;
3488 
3489 				// deal with symlinks properly
3490 				BEntry documentEntry(&ref, true);
3491 				if (appEntry == documentEntry) {
3492 					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3493 					copyOfRefs.RemoveData("refs", index);
3494 				} else {
3495 					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3496 					index++;
3497 				}
3498 			}
3499 
3500 			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3501 			team_id team;
3502 			error = be_roster->Launch(&app, refsToPass, &team);
3503 			if (error == B_ALREADY_RUNNING)
3504 				// app already running, not really an error
3505 				error = B_OK;
3506 			if (error == B_OK || mimesetIt != 0)
3507 				break;
3508 
3509 			SniffIfGeneric(&copyOfRefs);
3510 		}
3511 	}
3512 
3513 	if (error != B_OK && alertString.Length() == 0) {
3514 		BString loaderErrorString;
3515 		bool openedDocuments = true;
3516 
3517 		if (!refsToPass) {
3518 			// we just double clicked the app itself, do not offer to
3519 			// find a handling app
3520 			openWithOK = false;
3521 			openedDocuments = false;
3522 		}
3523 		if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) {
3524 			// We know it's an executable, but something unsupported
3525 			alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported "
3526 				"executable."));
3527 			alertString.ReplaceFirst("%name", app.name);
3528 		} else if (error == B_LEGACY_EXECUTABLE && !refsToPass) {
3529 			// For the moment, this marks an old R3 binary, we may want to
3530 			// extend it to gcc2 binaries someday post R1
3531 			alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. "
3532 				"Please obtain an updated version or recompile "
3533 				"the application."));
3534 			alertString.ReplaceFirst("%name", app.name);
3535 		} else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3536 			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3537 				"The file is mistakenly marked as executable. "));
3538 			alertString.ReplaceFirst("%name", app.name);
3539 
3540 			if (!openWithOK) {
3541 				// offer the possibility to change the permissions
3542 
3543 				alertString << B_TRANSLATE("\nShould this be fixed?");
3544 				BAlert* alert = new BAlert("", alertString.String(),
3545 					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3546 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3547 				alert->SetShortcut(0, B_ESCAPE);
3548 				if (alert->Go() == 1) {
3549 					BEntry entry(&documentRef);
3550 					mode_t permissions;
3551 
3552 					error = entry.GetPermissions(&permissions);
3553 					if (error == B_OK) {
3554 						error = entry.SetPermissions(permissions
3555 							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3556 					}
3557 					if (error == B_OK) {
3558 						// we updated the permissions, so let's try again
3559 						_TrackerLaunchDocuments(NULL, refs, false);
3560 						return;
3561 					} else {
3562 						alertString.SetTo(B_TRANSLATE("Could not update "
3563 							"permissions of file \"%name\". %error"));
3564 						alertString.ReplaceFirst("%name", app.name);
3565 						alertString.ReplaceFirst("%error", strerror(error));
3566 					}
3567 				} else
3568 					return;
3569 			}
3570 
3571 			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3572 		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3573 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3574 				"because application \"%app\" is in the Trash. "));
3575 			alertString.ReplaceFirst("%document", documentRef.name);
3576 			alertString.ReplaceFirst("%app", app.name);
3577 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3578 		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3579 			alertString.SetTo(
3580 				B_TRANSLATE("Could not open \"%name\" (%error). "));
3581 			alertString.ReplaceFirst("%name", documentRef.name);
3582 			alertString.ReplaceFirst("%error", strerror(error));
3583 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3584 		} else if (error == B_MISSING_SYMBOL
3585 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3586 			if (openedDocuments) {
3587 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3588 					"with application \"%app\" (Missing symbol: %symbol). "
3589 					"\n"));
3590 				alertString.ReplaceFirst("%document", documentRef.name);
3591 				alertString.ReplaceFirst("%app", app.name);
3592 				alertString.ReplaceFirst("%symbol",
3593 					loaderErrorString.String());
3594 			} else {
3595 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3596 					"(Missing symbol: %symbol). \n"));
3597 				alertString.ReplaceFirst("%document", documentRef.name);
3598 				alertString.ReplaceFirst("%symbol",
3599 					loaderErrorString.String());
3600 			}
3601 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3602 		} else if (error == B_MISSING_LIBRARY
3603 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3604 			if (openedDocuments) {
3605 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3606 					"with application \"%app\" (Missing libraries: %library). "
3607 					"\n"));
3608 				alertString.ReplaceFirst("%document", documentRef.name);
3609 				alertString.ReplaceFirst("%app", app.name);
3610 				alertString.ReplaceFirst("%library",
3611 					loaderErrorString.String());
3612 			} else {
3613 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3614 					"(Missing libraries: %library). \n"));
3615 				alertString.ReplaceFirst("%document", documentRef.name);
3616 				alertString.ReplaceFirst("%library",
3617 					loaderErrorString.String());
3618 			}
3619 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3620 		} else {
3621 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3622 				"application \"%app\" (%error). "));
3623 				alertString.ReplaceFirst("%document", documentRef.name);
3624 				alertString.ReplaceFirst("%app", app.name);
3625 				alertString.ReplaceFirst("%error", strerror(error));
3626 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3627 		}
3628 	}
3629 
3630 	if (error != B_OK) {
3631 		if (openWithOK) {
3632 			ASSERT(alternative);
3633 			alertString << alternative;
3634 			BAlert* alert = new BAlert("", alertString.String(),
3635 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3636 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3637 			alert->SetShortcut(0, B_ESCAPE);
3638 			if (alert->Go() == 1)
3639 				error = TrackerOpenWith(refs);
3640 		} else {
3641 			BAlert* alert = new BAlert("", alertString.String(),
3642 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3643 				B_WARNING_ALERT);
3644 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3645 			alert->Go();
3646 		}
3647 	}
3648 }
3649 
3650 // the following three calls don't return any reasonable error codes,
3651 // should fix that, making them void
3652 
3653 status_t
3654 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async,
3655 	bool openWithOK)
3656 {
3657 	if (!async)
3658 		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3659 	else {
3660 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3661 			openWithOK);
3662 	}
3663 
3664 	return B_OK;
3665 }
3666 
3667 status_t
3668 TrackerLaunch(const entry_ref* appRef, bool async)
3669 {
3670 	if (!async)
3671 		_TrackerLaunchAppWithDocuments(appRef, 0, false);
3672 	else
3673 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3674 
3675 	return B_OK;
3676 }
3677 
3678 status_t
3679 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK)
3680 {
3681 	if (!async)
3682 		_TrackerLaunchDocuments(0, refs, openWithOK);
3683 	else
3684 		AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK);
3685 
3686 	return B_OK;
3687 }
3688 
3689 status_t
3690 LaunchBrokenLink(const char* signature, const BMessage* refs)
3691 {
3692 	// This call is to support a hacky workaround for double-clicking
3693 	// broken refs for cifs
3694 	be_roster->Launch(signature, const_cast<BMessage*>(refs));
3695 	return B_OK;
3696 }
3697 
3698 // external launch calls; need to be robust, work if Tracker is not running
3699 
3700 #if !B_BEOS_VERSION_DANO
3701 _IMPEXP_TRACKER
3702 #endif
3703 status_t
3704 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived,
3705 	bool async, bool openWithOK)
3706 {
3707 	return TrackerLaunch(application, refsReceived, async, openWithOK);
3708 }
3709 
3710 
3711 #if !B_BEOS_VERSION_DANO
3712 _IMPEXP_TRACKER
3713 #endif
3714 status_t
3715 FSOpenWith(BMessage* listOfRefs)
3716 {
3717 	status_t result = B_ERROR;
3718 	listOfRefs->what = B_REFS_RECEIVED;
3719 
3720 	if (dynamic_cast<TTracker*>(be_app))
3721 		result = TrackerOpenWith(listOfRefs);
3722 	else
3723 		ASSERT(!"not yet implemented");
3724 
3725 	return result;
3726 }
3727 
3728 // legacy calls, need for compatibility
3729 
3730 void
3731 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents)
3732 {
3733 	TrackerLaunch(executable, documents, true);
3734 	delete documents;
3735 }
3736 
3737 status_t
3738 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs)
3739 {
3740 	BMessage temp(B_REFS_RECEIVED);
3741 	if (listOfRefs == NULL) {
3742 		ASSERT(ref);
3743 		temp.AddRef("refs", ref);
3744 		listOfRefs = &temp;
3745 	}
3746 	FSOpenWith(listOfRefs);
3747 
3748 	return B_OK;
3749 }
3750 
3751 status_t
3752 FSLaunchItem(const entry_ref* ref, BMessage* message, int32, bool async)
3753 {
3754 	if (message != NULL)
3755 		message->what = B_REFS_RECEIVED;
3756 
3757 	status_t result = TrackerLaunch(ref, message, async, true);
3758 	delete message;
3759 
3760 	return result;
3761 }
3762 
3763 
3764 void
3765 FSLaunchItem(const entry_ref* ref, BMessage* message, int32 workspace)
3766 {
3767 	FSLaunchItem(ref, message, workspace, true);
3768 }
3769 
3770 
3771 // Get the original path of an entry in the trash
3772 status_t
3773 FSGetOriginalPath(BEntry* entry, BPath* result)
3774 {
3775 	status_t err;
3776 	entry_ref ref;
3777 	err = entry->GetRef(&ref);
3778 	if (err != B_OK)
3779 		return err;
3780 
3781 	// Only call the routine for entries in the trash
3782 	if (!FSInTrashDir(&ref))
3783 		return B_ERROR;
3784 
3785 	BNode node(entry);
3786 	BString originalPath;
3787 	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3788 		// We're in luck, the entry has the original path in an attribute
3789 		err = result->SetTo(originalPath.String());
3790 		return err;
3791 	}
3792 
3793 	// Iterate the parent directories to find one with
3794 	// the original path attribute
3795 	BEntry parent(*entry);
3796 	err = parent.InitCheck();
3797 	if (err != B_OK)
3798 		return err;
3799 
3800 	// walk up the directory structure until we find a node
3801 	// with original path attribute
3802 	do {
3803 		// move to the parent of this node
3804 		err = parent.GetParent(&parent);
3805 		if (err != B_OK)
3806 			return err;
3807 
3808 		// return if we are at the root of the trash
3809 		if (FSIsTrashDir(&parent))
3810 			return B_ENTRY_NOT_FOUND;
3811 
3812 		// get the parent as a node
3813 		err = node.SetTo(&parent);
3814 		if (err != B_OK)
3815 			return err;
3816 	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3817 
3818 	// Found the attribute, figure out there this file
3819 	// used to live, based on the successfully-read attribute
3820 	err = result->SetTo(originalPath.String());
3821 	if (err != B_OK)
3822 		return err;
3823 
3824 	BPath path, pathParent;
3825 	err = parent.GetPath(&pathParent);
3826 	if (err != B_OK)
3827 		return err;
3828 
3829 	err = entry->GetPath(&path);
3830 	if (err != B_OK)
3831 		return err;
3832 
3833 	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3834 		// compute the new path by appending the offset of
3835 		// the item we are locating, to the original path
3836 		// of the parent
3837 
3838 	return B_OK;
3839 }
3840 
3841 
3842 directory_which
3843 WellKnowEntryList::Match(const node_ref* node)
3844 {
3845 	const WellKnownEntry* result = MatchEntry(node);
3846 	if (result != NULL)
3847 		return result->which;
3848 
3849 	return (directory_which)-1;
3850 }
3851 
3852 
3853 const WellKnowEntryList::WellKnownEntry*
3854 WellKnowEntryList::MatchEntry(const node_ref* node)
3855 {
3856 	if (self == NULL)
3857 		self = new WellKnowEntryList();
3858 
3859 	return self->MatchEntryCommon(node);
3860 }
3861 
3862 
3863 const WellKnowEntryList::WellKnownEntry*
3864 WellKnowEntryList::MatchEntryCommon(const node_ref* node)
3865 {
3866 	uint32 count = entries.size();
3867 	for (uint32 index = 0; index < count; index++) {
3868 		if (*node == entries[index].node)
3869 			return &entries[index];
3870 	}
3871 
3872 	return NULL;
3873 }
3874 
3875 
3876 void
3877 WellKnowEntryList::Quit()
3878 {
3879 	delete self;
3880 	self = NULL;
3881 }
3882 
3883 
3884 void
3885 WellKnowEntryList::AddOne(directory_which which, const char* name)
3886 {
3887 	BPath path;
3888 	if (find_directory(which, &path, true) != B_OK)
3889 		return;
3890 
3891 	BEntry entry(path.Path(), true);
3892 	node_ref node;
3893 	if (entry.GetNodeRef(&node) != B_OK)
3894 		return;
3895 
3896 	entries.push_back(WellKnownEntry(&node, which, name));
3897 }
3898 
3899 
3900 void
3901 WellKnowEntryList::AddOne(directory_which which, directory_which base,
3902 	const char* extra, const char* name)
3903 {
3904 	BPath path;
3905 	if (find_directory(base, &path, true) != B_OK)
3906 		return;
3907 
3908 	path.Append(extra);
3909 	BEntry entry(path.Path(), true);
3910 	node_ref node;
3911 	if (entry.GetNodeRef(&node) != B_OK)
3912 		return;
3913 
3914 	entries.push_back(WellKnownEntry(&node, which, name));
3915 }
3916 
3917 
3918 void
3919 WellKnowEntryList::AddOne(directory_which which, const char* path,
3920 	const char* name)
3921 {
3922 	BEntry entry(path, true);
3923 	node_ref node;
3924 	if (entry.GetNodeRef(&node) != B_OK)
3925 		return;
3926 
3927 	entries.push_back(WellKnownEntry(&node, which, name));
3928 }
3929 
3930 
3931 WellKnowEntryList::WellKnowEntryList()
3932 {
3933 #ifdef __HAIKU__
3934 	AddOne(B_SYSTEM_DIRECTORY, "system");
3935 #else
3936 	AddOne(B_BEOS_DIRECTORY, "beos");
3937 	AddOne(B_BEOS_SYSTEM_DIRECTORY, "system");
3938 #endif
3939 	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
3940 	AddOne(B_USER_DIRECTORY, "home");
3941 
3942 	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
3943 	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
3944 
3945 	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
3946 	AddOne(B_APPS_DIRECTORY, "apps");
3947 	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
3948 		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
3949 
3950 	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
3951 	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
3952 	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
3953 		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
3954 
3955 	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
3956 		"mail");
3957 
3958 	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
3959 		"queries", "queries");
3960 
3961 	AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop");
3962 	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
3963 		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
3964 
3965 	AddOne(B_USER_CONFIG_DIRECTORY, "config");
3966 
3967 	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
3968 		"people", "people");
3969 
3970 	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
3971 		"downloads", "downloads");
3972 }
3973 
3974 WellKnowEntryList* WellKnowEntryList::self = NULL;
3975 
3976 } // namespace BPrivate
3977