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