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