xref: /haiku/src/kits/tracker/FSUtils.cpp (revision e7be020ce59cd8a50dcb9a782b3b15cfa769396c)
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, size_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 	const entry_ref *destRefToCheck = NULL;
874 
875 	bool destIsTrash = false;
876 	BDirectory destDir;
877 	BDirectory *destDirToCheck = NULL;
878 	bool needPreflightNameCheck = false;
879 	bool sourceIsReadOnly = volume.IsReadOnly();
880 	volume.Unset();
881 
882 	bool fromUndo = FSIsUndoMoveMode(moveMode);
883 	moveMode = FSMoveMode(moveMode);
884 
885 	// if we're not passed a destEntry then we are supposed to move to trash
886 	if (destEntry != NULL) {
887 		destEntry->GetRef(&destRef);
888 		destRefToCheck = &destRef;
889 
890 		destDir.SetTo(destEntry);
891 		destDir.GetStat(&deststat);
892 		destDirToCheck = &destDir;
893 
894 		destVolumeDevice = deststat.st_dev;
895 		destIsTrash = FSIsTrashDir(destEntry);
896 		volume.SetTo(destVolumeDevice);
897 
898 		needPreflightNameCheck = true;
899 	} else if (moveMode == kDuplicateSelection) {
900 		BEntry entry;
901 		entry.SetTo(srcList->FirstItem());
902 		entry.GetParent(&destDir);
903 		volume.SetTo(srcVolumeDevice);
904 	} else {
905 		// move is to trash
906 		destIsTrash = true;
907 
908 		FSGetTrashDir(&destDir, srcVolumeDevice);
909 		volume.SetTo(srcVolumeDevice);
910 
911 		BEntry entry;
912 		destDir.GetEntry(&entry);
913 		destDirToCheck = &destDir;
914 
915 		entry.GetRef(&destRef);
916 		destRefToCheck = &destRef;
917 	}
918 
919 	// change the move mode if needed
920 	if (moveMode == kCopySelectionTo && destIsTrash) {
921 		// cannot copy to trash
922 		moveMode = kMoveSelectionTo;
923 	}
924 
925 	if (moveMode == kMoveSelectionTo && sourceIsReadOnly)
926 		moveMode = kCopySelectionTo;
927 
928 	bool needSizeCalculation = true;
929 	if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice)
930 		|| destIsTrash) {
931 		needSizeCalculation = false;
932 	}
933 
934 	// we need the undo object later on, so we create it no matter
935 	// if we really need it or not (it's very lightweight)
936 	MoveCopyUndo undo(srcList, destDir, pointList, moveMode);
937 	if (fromUndo)
938 		undo.Remove();
939 
940 	TrackerCopyLoopControl loopControl;
941 
942 	ConflictCheckResult conflictCheckResult = kPrompt;
943 	int32 collisionCount = 0;
944 	// TODO: Status item is created in InitCopy(), but it would be kind of
945 	// neat to move all that into TrackerCopyLoopControl
946 	status_t result = InitCopy(&loopControl, moveMode, srcList,
947 		&volume, destDirToCheck, &destRef, needPreflightNameCheck,
948 		needSizeCalculation, &collisionCount, &conflictCheckResult);
949 
950 	loopControl.SetSourceList(srcList);
951 
952 	if (result == B_OK) {
953 		for (int32 i = 0; i < srcList->CountItems(); i++) {
954 			BPoint *loc = (BPoint *)-1;
955 				// a loc of -1 forces autoplacement, rather than copying the
956 				// position of the original node
957 				// TODO:
958 				// Clean this mess up!
959 				// What could be a cleaner design is to pass along some kind
960 				// "filter" object that post-processes poses, i.e. adds the
961 				// location or other stuff. It should not be a job of the
962 				// copy-engine.
963 
964 			entry_ref *srcRef = srcList->ItemAt(i);
965 
966 			if (moveMode == kDuplicateSelection) {
967 				BEntry entry(srcRef);
968 				entry.GetParent(&destDir);
969 				destDir.GetStat(&deststat);
970 				volume.SetTo(srcRef->device);
971 			}
972 
973 			// handle case where item is dropped into folder it already lives
974 			// in which could happen if dragging from a query window
975 			if (moveMode != kCreateLink
976 				&& moveMode != kCreateRelativeLink
977 				&& moveMode != kDuplicateSelection
978 				&& !destIsTrash
979 				&& (srcRef->device == destRef.device
980 					&& srcRef->directory == deststat.st_ino))
981 				continue;
982 
983 			if (loopControl.CheckUserCanceled())
984 				break;
985 
986 			BEntry sourceEntry(srcRef);
987 			if (sourceEntry.InitCheck() != B_OK) {
988 				BString error(B_TRANSLATE("Error moving \"%name\"."));
989 				error.ReplaceFirst("%name", srcRef->name);
990 				BAlert* alert = new BAlert("", error.String(),
991 					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
992 					B_WARNING_ALERT);
993 				alert->SetShortcut(0, B_ESCAPE);
994 				alert->Go();
995 				break;
996 			}
997 
998 			// are we moving item to trash?
999 			if (destIsTrash) {
1000 				if (pointList)
1001 					loc = (BPoint *)pointList->ItemAt(i);
1002 
1003 				result = MoveEntryToTrash(&sourceEntry, loc, undo);
1004 				if (result != B_OK) {
1005 					BString error(B_TRANSLATE("Error moving \"%name\" to Trash. (%error)"));
1006 					error.ReplaceFirst("%name", srcRef->name);
1007 					error.ReplaceFirst("%error", strerror(result));
1008 					BAlert* alert = new BAlert("", error.String(),
1009 						B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1010 						B_WARNING_ALERT);
1011 					alert->SetShortcut(0, B_ESCAPE);
1012 					alert->Go();
1013 					break;
1014 				}
1015 				continue;
1016 			}
1017 
1018 			// resolve name collisions and hierarchy problems
1019 			if (CheckName(moveMode, &sourceEntry, &destDir, collisionCount > 1,
1020 				conflictCheckResult) != B_OK) {
1021 				// we will skip the current item, because we got a conflict
1022 				// and were asked to or because there was some conflict
1023 
1024 				// update the status because item got skipped and the status
1025 				// will not get updated by the move call
1026 				loopControl.UpdateStatus(srcRef->name, *srcRef, 1);
1027 
1028 				continue;
1029 			}
1030 
1031 			// get location to place this item
1032 			if (pointList && moveMode != kCopySelectionTo) {
1033 				loc = (BPoint *)pointList->ItemAt(i);
1034 
1035 				BNode *src_node = GetWritableNode(&sourceEntry);
1036 				if (src_node && src_node->InitCheck() == B_OK) {
1037 					PoseInfo poseInfo;
1038 					poseInfo.fInvisible = false;
1039 					poseInfo.fInitedDirectory = deststat.st_ino;
1040 					poseInfo.fLocation = *loc;
1041 					src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
1042 						&poseInfo, sizeof(poseInfo));
1043 				}
1044 				delete src_node;
1045 			}
1046 
1047 			if (pointList)
1048  				loc = (BPoint*)pointList->ItemAt(i);
1049 
1050 			result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL,
1051 				undo, &loopControl);
1052 			if (result != B_OK)
1053 				break;
1054 		}
1055 	}
1056 
1057 	// duplicates of srcList, destFolder were created - dispose them
1058 	delete srcList;
1059 	delete destEntry;
1060 
1061 	// delete file location list and all Points within
1062 	if (pointList) {
1063 		pointList->DoForEach(delete_point);
1064 		delete pointList;
1065 	}
1066 
1067 	return B_OK;
1068 }
1069 
1070 class FailWithAlert {
1071 	public:
1072 		static void FailOnError(status_t error, const char *string,
1073 			const char *name = NULL)
1074 		{
1075 			if (error != B_OK)
1076 				throw FailWithAlert(error, string, name);
1077 		}
1078 
1079 		FailWithAlert(status_t error, const char *string, const char *name)
1080 		:	fString(string),
1081 			fName(name),
1082 			fError(error)
1083 		{
1084 		}
1085 
1086 		const char *fString;
1087 		const char *fName;
1088 		status_t fError;
1089 };
1090 
1091 class MoveError {
1092 	public:
1093 		static void FailOnError(status_t error)
1094 		{
1095 			if (error != B_OK)
1096 				throw MoveError(error);
1097 		}
1098 
1099 		MoveError(status_t error)
1100 		:	fError(error)
1101 		{ }
1102 
1103 		status_t fError;
1104 };
1105 
1106 
1107 void
1108 CopyFile(BEntry *srcFile, StatStruct *srcStat, BDirectory *destDir,
1109 	CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName,
1110 	Undo &undo)
1111 {
1112 	if (loopControl->SkipEntry(srcFile, true))
1113 		return;
1114 
1115 	node_ref node;
1116 	destDir->GetNodeRef(&node);
1117 	BVolume volume(node.device);
1118 
1119 	// check for free space first
1120 	if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) {
1121 		loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "",
1122 			B_DEVICE_FULL, false);
1123 		throw (status_t)B_DEVICE_FULL;
1124 	}
1125 
1126 	char destName[B_FILE_NAME_LENGTH];
1127 	srcFile->GetName(destName);
1128 	entry_ref ref;
1129 	srcFile->GetRef(&ref);
1130 
1131 	loopControl->UpdateStatus(destName, ref, 1024, true);
1132 
1133 	if (makeOriginalName) {
1134 		BString suffix(" ");
1135 		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1136 		FSMakeOriginalName(destName, destDir, suffix.String());
1137 		undo.UpdateEntry(srcFile, destName);
1138 	}
1139 
1140 	BEntry conflictingEntry;
1141 	if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) {
1142 		switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir,
1143 			false, false)) {
1144 			case TrackerCopyLoopControl::kSkip:
1145 				// we are about to ignore this entire directory
1146 				return;
1147 
1148 			case TrackerCopyLoopControl::kReplace:
1149 				if (!conflictingEntry.IsDirectory()) {
1150 					ThrowOnError(conflictingEntry.Remove());
1151 					break;
1152 				}
1153 				// fall through if not a directory
1154 			case TrackerCopyLoopControl::kMerge:
1155 				// This flag implies that the attributes should be kept
1156 				// on the file.  Just ignore it.
1157 				break;
1158 		}
1159 	}
1160 
1161 	try {
1162 		LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc);
1163 	} catch (status_t err) {
1164 		if (err == kCopyCanceled)
1165 			throw (status_t)err;
1166 
1167 		if (err != B_OK) {
1168 			if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT(kFileErrorString),
1169 					destName, err, true)) {
1170 				throw (status_t)err;
1171 			} else {
1172 				// user selected continue in spite of error, update status bar
1173 				loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size);
1174 			}
1175 		}
1176 	}
1177 }
1178 
1179 
1180 #ifdef _SILENTLY_CORRECT_FILE_NAMES
1181 static bool
1182 CreateFileSystemCompatibleName(const BDirectory *destDir, char *destName)
1183 {
1184 	// Is it a FAT32 file system? (this is the only one we currently now about)
1185 
1186 	BEntry target;
1187 	destDir->GetEntry(&target);
1188 	entry_ref targetRef;
1189 	fs_info info;
1190 	if (target.GetRef(&targetRef) == B_OK
1191 		&& fs_stat_dev(targetRef.device, &info) == B_OK
1192 		&& !strcmp(info.fsh_name, "fat")) {
1193 		bool wasInvalid = false;
1194 
1195 		// it's a FAT32 file system, now check the name
1196 
1197 		int32 length = strlen(destName) - 1;
1198 		while (destName[length] == '.') {
1199 			// invalid name, just cut off the dot at the end
1200 			destName[length--] = '\0';
1201 			wasInvalid = true;
1202 		}
1203 
1204 		char *invalid = destName;
1205 		while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) {
1206 			invalid[0] = '_';
1207 			wasInvalid = true;
1208 		}
1209 
1210 		return wasInvalid;
1211 	}
1212 
1213 	return false;
1214 }
1215 #endif
1216 
1217 
1218 static void
1219 LowLevelCopy(BEntry *srcEntry, StatStruct *srcStat, BDirectory *destDir,
1220 	char *destName, CopyLoopControl *loopControl, BPoint *loc)
1221 {
1222 	entry_ref ref;
1223 	ThrowOnError(srcEntry->GetRef(&ref));
1224 
1225 	if (S_ISLNK(srcStat->st_mode)) {
1226 		// handle symbolic links
1227 		BSymLink srcLink;
1228 		BSymLink newLink;
1229 		char linkpath[MAXPATHLEN];
1230 
1231 		ThrowOnError(srcLink.SetTo(srcEntry));
1232 		ThrowIfNotSize(srcLink.ReadLink(linkpath, MAXPATHLEN-1));
1233 
1234 		ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink));
1235 
1236 		node_ref destNodeRef;
1237 		destDir->GetNodeRef(&destNodeRef);
1238 		// copy or write new pose location as a first thing
1239 		SetUpPoseLocation(ref.directory, destNodeRef.node, &srcLink,
1240 			&newLink, loc);
1241 
1242 		BNodeInfo nodeInfo(&newLink);
1243 		nodeInfo.SetType(B_LINK_MIMETYPE);
1244 
1245 		newLink.SetPermissions(srcStat->st_mode);
1246 		newLink.SetOwner(srcStat->st_uid);
1247 		newLink.SetGroup(srcStat->st_gid);
1248 		newLink.SetModificationTime(srcStat->st_mtime);
1249 		newLink.SetCreationTime(srcStat->st_crtime);
1250 
1251 		return;
1252 	}
1253 
1254 	BFile srcFile(srcEntry, O_RDONLY);
1255 	ThrowOnInitCheckError(&srcFile);
1256 
1257 	const size_t kMinBufferSize = 1024 * 128;
1258 	const size_t kMaxBufferSize = 1024 * 1024;
1259 
1260 	size_t bufsize = kMinBufferSize;
1261 	if (bufsize < srcStat->st_size) {
1262 		//	File bigger than the buffer size: determine an optimal buffer size
1263 		system_info sinfo;
1264 		get_system_info(&sinfo);
1265 		size_t freesize = static_cast<size_t>(
1266 			(sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE);
1267 		bufsize = freesize / 4;					// take 1/4 of RAM max
1268 		bufsize -= bufsize % (16 * 1024);		// Round to 16 KB boundaries
1269 		if (bufsize < kMinBufferSize)			// at least kMinBufferSize
1270 			bufsize = kMinBufferSize;
1271 		else if (bufsize > kMaxBufferSize)		// no more than kMaxBufferSize
1272 			bufsize = kMaxBufferSize;
1273 	}
1274 
1275 	BFile destFile(destDir, destName, O_RDWR | O_CREAT);
1276 #ifdef _SILENTLY_CORRECT_FILE_NAMES
1277 	if ((destFile.InitCheck() == B_BAD_VALUE
1278 		|| destFile.InitCheck() == B_NOT_ALLOWED)
1279 		&& CreateFileSystemCompatibleName(destDir, destName)) {
1280 		destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE);
1281 	}
1282 #endif
1283 
1284 	ThrowOnInitCheckError(&destFile);
1285 
1286 	node_ref destNodeRef;
1287 	destDir->GetNodeRef(&destNodeRef);
1288 	// copy or write new pose location as a first thing
1289 	SetUpPoseLocation(ref.directory, destNodeRef.node, &srcFile,
1290 		&destFile, loc);
1291 
1292 	char *buffer = new char[bufsize];
1293 	try {
1294 		// copy data portion of file
1295 		while (true) {
1296 			if (loopControl->CheckUserCanceled()) {
1297 				// if copy was canceled, remove partial destination file
1298 				destFile.Unset();
1299 
1300 				BEntry destEntry;
1301 				if (destDir->FindEntry(destName, &destEntry) == B_OK)
1302 					destEntry.Remove();
1303 
1304 				throw (status_t)kCopyCanceled;
1305 			}
1306 
1307 			ASSERT(buffer);
1308 			ssize_t bytes = srcFile.Read(buffer, bufsize);
1309 
1310 			if (bytes > 0) {
1311 				ssize_t updateBytes = 0;
1312 				if (bytes > 32 * 1024) {
1313 					// when copying large chunks, update after read and after
1314 					// write to get better update granularity
1315 					updateBytes = bytes / 2;
1316 					loopControl->UpdateStatus(NULL, ref, updateBytes, true);
1317 				}
1318 
1319 				loopControl->ChecksumChunk(buffer, (size_t)bytes);
1320 
1321 				ssize_t result = destFile.Write(buffer, (size_t)bytes);
1322 				if (result != bytes)
1323 					throw (status_t)B_ERROR;
1324 
1325 				loopControl->UpdateStatus(NULL, ref, bytes - updateBytes,
1326 					true);
1327 			} else if (bytes < 0) {
1328 				// read error
1329 				throw (status_t)bytes;
1330 			} else {
1331 				// we are done
1332 				break;
1333 			}
1334 		}
1335 
1336 		CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize);
1337 	} catch (...) {
1338 		delete [] buffer;
1339 		throw;
1340 	}
1341 
1342 	destFile.SetPermissions(srcStat->st_mode);
1343 	destFile.SetOwner(srcStat->st_uid);
1344 	destFile.SetGroup(srcStat->st_gid);
1345 	destFile.SetModificationTime(srcStat->st_mtime);
1346 	destFile.SetCreationTime(srcStat->st_crtime);
1347 
1348 	delete [] buffer;
1349 
1350 	if (!loopControl->ChecksumFile(&ref)) {
1351 		// File no good.  Remove and quit.
1352 		destFile.Unset();
1353 
1354 		BEntry destEntry;
1355 		if (destDir->FindEntry(destName, &destEntry) == B_OK)
1356 			destEntry.Remove();
1357 		throw (status_t)kUserCanceled;
1358 	}
1359 }
1360 
1361 
1362 void
1363 CopyAttributes(CopyLoopControl *control, BNode *srcNode, BNode *destNode,
1364 	void *buffer, size_t bufsize)
1365 {
1366 	// ToDo:
1367 	// Add error checking
1368 	// prior to coyping attributes, make sure indices are installed
1369 
1370 	// When calling CopyAttributes on files, have to make sure destNode
1371 	// is a BFile opened R/W
1372 
1373 	srcNode->RewindAttrs();
1374 	char name[256];
1375 	while (srcNode->GetNextAttrName(name) == B_OK) {
1376 		// Check to see if this attribute should be skipped.
1377 		if (control->SkipAttribute(name))
1378 			continue;
1379 
1380 		attr_info info;
1381 		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1382 			continue;
1383 
1384 		// Check to see if this attribute should be overwritten when it
1385 		// already exists.
1386 		if (control->PreserveAttribute(name)) {
1387 			attr_info dest_info;
1388 			if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1389 				continue;
1390 		}
1391 
1392 		// Special case for a size 0 attribute. It wouldn't be written at all
1393 		// otherwise.
1394 		if (info.size == 0)
1395 			destNode->WriteAttr(name, info.type, 0, buffer, 0);
1396 
1397 		ssize_t bytes;
1398 		ssize_t numToRead = (ssize_t)info.size;
1399 		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1400 			size_t chunkSize = (size_t)numToRead;
1401 			if (chunkSize > bufsize)
1402 				chunkSize = bufsize;
1403 
1404 			bytes = srcNode->ReadAttr(name, info.type, offset,
1405 				buffer, chunkSize);
1406 
1407 			if (bytes <= 0)
1408 				break;
1409 
1410 			destNode->WriteAttr(name, info.type, offset, buffer,
1411 				(size_t)bytes);
1412 
1413 			numToRead -= bytes;
1414 		}
1415 	}
1416 }
1417 
1418 
1419 static void
1420 CopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl,
1421 	BPoint *loc, bool makeOriginalName, Undo &undo, bool removeSource = false)
1422 {
1423 	BDirectory newDir;
1424 	BEntry entry;
1425 	status_t err = B_OK;
1426 	bool createDirectory = true;
1427 	BEntry existingEntry;
1428 
1429 	if (loopControl->SkipEntry(srcEntry, false))
1430 		return;
1431 
1432 	entry_ref ref;
1433 	srcEntry->GetRef(&ref);
1434 
1435 	char destName[B_FILE_NAME_LENGTH];
1436 	strcpy(destName, ref.name);
1437 
1438 	loopControl->UpdateStatus(ref.name, ref, 1024, true);
1439 
1440 	if (makeOriginalName) {
1441 		BString suffix(" ");
1442 		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1443 		FSMakeOriginalName(destName, destDir, suffix.String());
1444 		undo.UpdateEntry(srcEntry, destName);
1445 	}
1446 
1447 	if (destDir->FindEntry(destName, &existingEntry) == B_OK) {
1448 		// some entry with a conflicting name is already present in destDir
1449 		// decide what to do about it
1450 		bool isDirectory = existingEntry.IsDirectory();
1451 
1452 		switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir,
1453 			true, isDirectory)) {
1454 			case TrackerCopyLoopControl::kSkip:
1455 				// we are about to ignore this entire directory
1456 				return;
1457 
1458 
1459 			case TrackerCopyLoopControl::kReplace:
1460 				if (!isDirectory) {
1461 					// conflicting with a file or symbolic link, remove entry
1462 					ThrowOnError(existingEntry.Remove());
1463 					break;
1464 				}
1465 			// fall through if directory, do not replace.
1466 			case TrackerCopyLoopControl::kMerge:
1467 				ASSERT(isDirectory);
1468 				// do not create a new directory, use the current one
1469 				newDir.SetTo(&existingEntry);
1470 				createDirectory = false;
1471 				break;
1472 		}
1473 	}
1474 
1475 	// loop through everything in src folder and copy it to new folder
1476 	BDirectory srcDir(srcEntry);
1477 	srcDir.Rewind();
1478 
1479 	// create a new folder inside of destination folder
1480 	if (createDirectory) {
1481 	 	err = destDir->CreateDirectory(destName, &newDir);
1482 #ifdef _SILENTLY_CORRECT_FILE_NAMES
1483 	 	if (err == B_BAD_VALUE) {
1484 	 		// check if it's an invalid name on a FAT32 file system
1485 	 		if (CreateFileSystemCompatibleName(destDir, destName))
1486 	 			err = destDir->CreateDirectory(destName, &newDir);
1487 	 	}
1488 #endif
1489 		if (err != B_OK) {
1490 			if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT(
1491 					kFolderErrorString), destName, err, true)) {
1492 				throw err;
1493 			}
1494 
1495 			// will allow rest of copy to continue
1496 			return;
1497 		}
1498 	}
1499 
1500 	char *buffer;
1501 	if (createDirectory && err == B_OK
1502 		&& (buffer = (char*)malloc(32768)) != 0) {
1503 		CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768);
1504 			// don't copy original pose location if new location passed
1505 		free(buffer);
1506 	}
1507 
1508 	StatStruct statbuf;
1509 	srcDir.GetStat(&statbuf);
1510 	dev_t sourceDeviceID = statbuf.st_dev;
1511 
1512 	// copy or write new pose location
1513 	node_ref destNodeRef;
1514 	destDir->GetNodeRef(&destNodeRef);
1515 	SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir,
1516 		&newDir, loc);
1517 
1518 	while (srcDir.GetNextEntry(&entry) == B_OK) {
1519 
1520 		if (loopControl->CheckUserCanceled())
1521 			throw (status_t)kUserCanceled;
1522 
1523 		entry.GetStat(&statbuf);
1524 
1525 		if (S_ISDIR(statbuf.st_mode)) {
1526 
1527 			// entry is a mount point, do not copy it
1528 			if (statbuf.st_dev != sourceDeviceID) {
1529 				PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n",
1530 					statbuf.st_dev, sourceDeviceID));
1531 				continue;
1532 			}
1533 
1534 			CopyFolder(&entry, &newDir, loopControl, 0, false, undo,
1535 				removeSource);
1536 			if (removeSource)
1537 				FSDeleteFolder(&entry, loopControl, true, true, false);
1538 		} else {
1539 			CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo);
1540 			if (removeSource)
1541 				entry.Remove();
1542 		}
1543 	}
1544 	if (removeSource)
1545 		srcEntry->Remove();
1546 	else
1547 		srcEntry->Unset();
1548 }
1549 
1550 
1551 status_t
1552 RecursiveMove(BEntry *entry, BDirectory *destDir,
1553 	CopyLoopControl* loopControl)
1554 {
1555 	char name[B_FILE_NAME_LENGTH];
1556 	if (entry->GetName(name) == B_OK) {
1557 		if (destDir->Contains(name)) {
1558 			BPath path (destDir, name);
1559 			BDirectory subDir (path.Path());
1560 			entry_ref ref;
1561 			entry->GetRef(&ref);
1562 			BDirectory source(&ref);
1563 			if (source.InitCheck() == B_OK) {
1564 				source.Rewind();
1565 				BEntry current;
1566 				while (source.GetNextEntry(&current) == B_OK) {
1567 					if (current.IsDirectory()) {
1568 						RecursiveMove(&current, &subDir, loopControl);
1569 						current.Remove();
1570 					} else {
1571 						current.GetName(name);
1572 						if (loopControl->OverwriteOnConflict(&current, name,
1573 							&subDir, true, false)
1574 								!= TrackerCopyLoopControl::kSkip) {
1575 							MoveError::FailOnError(current.MoveTo(&subDir,
1576 								NULL, true));
1577 						}
1578 					}
1579 				}
1580 			}
1581 			entry->Remove();
1582 		} else {
1583 			MoveError::FailOnError(entry->MoveTo(destDir));
1584 		}
1585 	}
1586 
1587 	return B_OK;
1588 }
1589 
1590 status_t
1591 MoveItem(BEntry *entry, BDirectory *destDir, BPoint *loc, uint32 moveMode,
1592 	const char *newName, Undo &undo, CopyLoopControl* loopControl)
1593 {
1594 	entry_ref ref;
1595 	try {
1596 		node_ref destNode;
1597 		StatStruct statbuf;
1598 		MoveError::FailOnError(entry->GetStat(&statbuf));
1599 		MoveError::FailOnError(entry->GetRef(&ref));
1600 		MoveError::FailOnError(destDir->GetNodeRef(&destNode));
1601 
1602 		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
1603 			PoseInfo poseInfo;
1604 			char name[B_FILE_NAME_LENGTH];
1605 			strcpy(name, ref.name);
1606 
1607 			BSymLink link;
1608 			BString suffix(" ");
1609 			suffix << B_TRANSLATE_COMMENT("link", "filename link"),
1610 			FSMakeOriginalName(name, destDir, suffix.String());
1611 			undo.UpdateEntry(entry, name);
1612 
1613 			BPath path;
1614 			entry->GetPath(&path);
1615 			if (loc && loc != (BPoint *)-1) {
1616 				poseInfo.fInvisible = false;
1617 				poseInfo.fInitedDirectory = destNode.node;
1618 				poseInfo.fLocation = *loc;
1619 			}
1620 
1621 			status_t err = B_ERROR;
1622 
1623 			if (moveMode == kCreateRelativeLink) {
1624 				if (statbuf.st_dev == destNode.device) {
1625 					// relative link only works on the same device
1626 					char oldwd[B_PATH_NAME_LENGTH];
1627 					getcwd(oldwd, B_PATH_NAME_LENGTH);
1628 
1629 					BEntry destEntry;
1630 					destDir -> GetEntry(&destEntry);
1631 					BPath destPath;
1632 					destEntry.GetPath(&destPath);
1633 
1634 					chdir(destPath.Path());
1635 						// change working dir to target dir
1636 
1637 					BString destString(destPath.Path());
1638 					destString.Append("/");
1639 
1640 					BString srcString(path.Path());
1641 					srcString.RemoveLast(path.Leaf());
1642 
1643 					// find index while paths are the same
1644 
1645 					const char *src = srcString.String();
1646 					const char *dest = destString.String();
1647 					const char *lastFolderSrc = src;
1648 					const char *lastFolderDest = dest;
1649 
1650 					while (*src && *dest && *src == *dest) {
1651 						++src;
1652 						if (*dest++ == '/') {
1653 							lastFolderSrc = src;
1654 							lastFolderDest = dest;
1655 						}
1656 					}
1657 					src = lastFolderSrc;
1658 					dest = lastFolderDest;
1659 
1660 					BString source;
1661 					if (*dest == '\0' && *src != '\0') {
1662 						// source is deeper in the same tree than the target
1663 						source.Append(src);
1664 					} else if (*dest != '\0') {
1665 						// target is deeper in the same tree than the source
1666 						while (*dest) {
1667 							if (*dest == '/')
1668 								source.Prepend("../");
1669 							++dest;
1670 						}
1671 						source.Append(src);
1672 					}
1673 
1674 					// else source and target are in the same dir
1675 
1676 					source.Append(path.Leaf());
1677 					err = destDir->CreateSymLink(name, source.String(), &link);
1678 
1679 					chdir(oldwd);
1680 						// change working dir back to original
1681 				} else
1682 					moveMode = kCreateLink;
1683 						// fall back to absolute link mode
1684 			}
1685 
1686 			if (moveMode == kCreateLink)
1687 				err = destDir->CreateSymLink(name, path.Path(), &link);
1688 
1689 			if (err == B_UNSUPPORTED) {
1690 				throw FailWithAlert(err,
1691 					B_TRANSLATE("The target disk does not support "
1692 					"creating links."), NULL);
1693 			}
1694 
1695 			FailWithAlert::FailOnError(err,
1696 				B_TRANSLATE("Error creating link to \"%name\"."),
1697 				ref.name);
1698 
1699 			if (loc && loc != (BPoint *)-1) {
1700 				link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
1701 					sizeof(PoseInfo));
1702 			}
1703 
1704 			BNodeInfo nodeInfo(&link);
1705 			nodeInfo.SetType(B_LINK_MIMETYPE);
1706 			return B_OK;
1707 		}
1708 
1709 		// if move is on same volume don't copy
1710 		if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo
1711 			&& moveMode != kDuplicateSelection) {
1712 
1713 			// for "Move" the size for status is always 1 - since file
1714 			// size is irrelevant when simply moving to a new folder
1715 			loopControl->UpdateStatus(ref.name, ref, 1);
1716 			if (entry->IsDirectory())
1717 				return RecursiveMove(entry, destDir, loopControl);
1718 			MoveError::FailOnError(entry->MoveTo(destDir, newName));
1719 		} else {
1720 			bool makeOriginalName = (moveMode == kDuplicateSelection);
1721 			if (S_ISDIR(statbuf.st_mode)) {
1722 				CopyFolder(entry, destDir, loopControl, loc, makeOriginalName,
1723 					undo, moveMode == kMoveSelectionTo);
1724 			} else {
1725 				CopyFile(entry, &statbuf, destDir, loopControl, loc,
1726 					makeOriginalName, undo);
1727 				if (moveMode == kMoveSelectionTo)
1728 					entry->Remove();
1729 			}
1730 		}
1731 	} catch (status_t error) {
1732 		// no alert, was already taken care of before
1733 		return error;
1734 	} catch (MoveError error) {
1735 		BString errorString(B_TRANSLATE("Error moving \"%name\""));
1736 		errorString.ReplaceFirst("%name", ref.name);
1737 		(new BAlert("", errorString.String(), B_TRANSLATE("OK"), 0, 0,
1738 			B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
1739 		return error.fError;
1740 	} catch (FailWithAlert error) {
1741 		BString buffer(error.fString);
1742 		if (error.fName)
1743 			buffer.ReplaceFirst("%name", error.fName);
1744 		else
1745 			buffer <<  error.fString;
1746 		(new BAlert("", buffer.String(), B_TRANSLATE("OK"),	0, 0,
1747 			B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
1748 
1749 		return error.fError;
1750 	}
1751 
1752 	return B_OK;
1753 }
1754 
1755 
1756 void
1757 FSDuplicate(BObjectList<entry_ref> *srcList, BList *pointList)
1758 {
1759 	LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList,
1760 		(BEntry *)NULL, pointList, kDuplicateSelection);
1761 }
1762 
1763 
1764 #if 0
1765 status_t
1766 FSCopyFolder(BEntry *srcEntry, BDirectory *destDir,
1767 	CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName)
1768 {
1769 	try
1770 		CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName);
1771 	catch (status_t error) {
1772 		return error;
1773 
1774 	return B_OK;
1775 }
1776 #endif
1777 
1778 
1779 status_t
1780 FSCopyAttributesAndStats(BNode *srcNode, BNode *destNode)
1781 {
1782 	char *buffer = new char[1024];
1783 
1784 	// copy the attributes
1785 	srcNode->RewindAttrs();
1786 	char name[256];
1787 	while (srcNode->GetNextAttrName(name) == B_OK) {
1788 		attr_info info;
1789 		if (srcNode->GetAttrInfo(name, &info) != B_OK)
1790 			continue;
1791 
1792 		attr_info dest_info;
1793 		if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1794 			continue;
1795 
1796 		ssize_t bytes;
1797 		ssize_t numToRead = (ssize_t)info.size;
1798 		for (off_t offset = 0; numToRead > 0; offset += bytes) {
1799 			size_t chunkSize = (size_t)numToRead;
1800 			if (chunkSize > 1024)
1801 				chunkSize = 1024;
1802 
1803 			bytes = srcNode->ReadAttr(name, info.type, offset, buffer,
1804 				chunkSize);
1805 
1806 			if (bytes <= 0)
1807 				break;
1808 
1809 			destNode->WriteAttr(name, info.type, offset, buffer,
1810 				(size_t)bytes);
1811 
1812 			numToRead -= bytes;
1813 		}
1814 	}
1815 	delete[] buffer;
1816 
1817 	// copy the file stats
1818 	struct stat srcStat;
1819 	srcNode->GetStat(&srcStat);
1820 	destNode->SetPermissions(srcStat.st_mode);
1821 	destNode->SetOwner(srcStat.st_uid);
1822 	destNode->SetGroup(srcStat.st_gid);
1823 	destNode->SetModificationTime(srcStat.st_mtime);
1824 	destNode->SetCreationTime(srcStat.st_crtime);
1825 
1826 	return B_OK;
1827 }
1828 
1829 
1830 #if 0
1831 status_t
1832 FSCopyFile(BEntry* srcFile, StatStruct *srcStat, BDirectory* destDir,
1833 	CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName)
1834 {
1835 	try {
1836 		CopyFile(srcFile, srcStat, destDir, loopControl, loc,
1837 			makeOriginalName);
1838 	} catch (status_t error) {
1839 		return error;
1840 	}
1841 
1842 	return B_OK;
1843 }
1844 #endif
1845 
1846 
1847 static status_t
1848 MoveEntryToTrash(BEntry *entry, BPoint *loc, Undo &undo)
1849 {
1850 	BDirectory trash_dir;
1851 	entry_ref ref;
1852 	status_t result = entry->GetRef(&ref);
1853 	if (result != B_OK)
1854 		return result;
1855 
1856 	node_ref nodeRef;
1857 	result = entry->GetNodeRef(&nodeRef);
1858 	if (result != B_OK)
1859 		return result;
1860 
1861 	StatStruct statbuf;
1862 	result = entry->GetStat(&statbuf);
1863 	if (entry->GetStat(&statbuf) != B_OK)
1864 		return result;
1865 
1866 	// if it's a directory close the window and any child dir windows
1867 	if (S_ISDIR(statbuf.st_mode)) {
1868 		BDirectory dir(entry);
1869 
1870 		// if it's a volume, try to unmount
1871 		if (dir.IsRootDirectory()) {
1872 			BVolume	volume(nodeRef.device);
1873 			BVolume	boot;
1874 
1875 			BVolumeRoster().GetBootVolume(&boot);
1876 			if (volume == boot) {
1877 				char name[B_FILE_NAME_LENGTH];
1878 				volume.GetName(name);
1879 				BString buffer(B_TRANSLATE("Cannot unmount the boot volume \"%name\"."));
1880 				buffer.ReplaceFirst("%name", name);
1881 				BAlert* alert = new BAlert("", buffer.String(),
1882 					B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1883 					B_WARNING_ALERT);
1884 				alert->SetShortcut(0, B_ESCAPE);
1885 				alert->Go();
1886 			} else {
1887 				BMessage message(kUnmountVolume);
1888 				message.AddInt32("device_id", volume.Device());
1889 				be_app->PostMessage(&message);
1890 			}
1891 			return B_OK;
1892 		}
1893 
1894 		// get trash directory on same volume as item being moved
1895 		result = FSGetTrashDir(&trash_dir, nodeRef.device);
1896 		if (result != B_OK)
1897 			return result;
1898 
1899 		// check hierarchy before moving
1900 		BEntry trashEntry;
1901 		trash_dir.GetEntry(&trashEntry);
1902 
1903 		if (dir == trash_dir || dir.Contains(&trashEntry)) {
1904 			(new BAlert("",
1905 				B_TRANSLATE("You cannot put the Trash, home or Desktop "
1906 				"directory into the trash."),
1907 				B_TRANSLATE("OK"),
1908 				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
1909 
1910 			// return no error so we don't get two dialogs
1911 			return B_OK;
1912 		}
1913 
1914 		BMessage message(kCloseWindowAndChildren);
1915 
1916 		node_ref parentNode;
1917 		parentNode.device = statbuf.st_dev;
1918 		parentNode.node = statbuf.st_ino;
1919 		message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref));
1920 		be_app->PostMessage(&message);
1921 	} else {
1922 		// get trash directory on same volume as item being moved
1923 		result = FSGetTrashDir(&trash_dir, nodeRef.device);
1924 		if (result != B_OK)
1925 			return result;
1926 	}
1927 
1928 	// make sure name doesn't conflict with anything in trash already
1929 	char name[B_FILE_NAME_LENGTH];
1930 	strcpy(name, ref.name);
1931 	if (trash_dir.Contains(name)) {
1932 		BString suffix(" ");
1933 		suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1934 		FSMakeOriginalName(name, &trash_dir, suffix.String());
1935 		undo.UpdateEntry(entry, name);
1936 	}
1937 
1938 	BNode *src_node = 0;
1939 	if (loc && loc != (BPoint *)-1
1940 		&& (src_node = GetWritableNode(entry, &statbuf)) != 0) {
1941 		trash_dir.GetStat(&statbuf);
1942 		PoseInfo poseInfo;
1943 		poseInfo.fInvisible = false;
1944 		poseInfo.fInitedDirectory = statbuf.st_ino;
1945 		poseInfo.fLocation = *loc;
1946 		src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
1947 			sizeof(poseInfo));
1948 		delete src_node;
1949 	}
1950 
1951 	BNode node(entry);
1952 	BPath path;
1953 	// Get path of entry before it's moved to the trash
1954 	// and write it to the file as an attribute
1955 	if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) {
1956 		BString originalPath(path.Path());
1957 		node.WriteAttrString(kAttrOriginalPath, &originalPath);
1958 	}
1959 
1960 	TrackerCopyLoopControl loopControl;
1961 	MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo,
1962 		&loopControl);
1963 	return B_OK;
1964 }
1965 
1966 
1967 ConflictCheckResult
1968 PreFlightNameCheck(BObjectList<entry_ref> *srcList, const BDirectory *destDir,
1969 	int32 *collisionCount, uint32 moveMode)
1970 {
1971 
1972 	// count the number of name collisions in dest folder
1973 	*collisionCount = 0;
1974 
1975 	int32 count = srcList->CountItems();
1976 	for (int32 i = 0; i < count; i++) {
1977 		entry_ref *srcRef = srcList->ItemAt(i);
1978 		BEntry entry(srcRef);
1979 		BDirectory parent;
1980 		entry.GetParent(&parent);
1981 
1982 		if (parent != *destDir) {
1983 			if (destDir->Contains(srcRef->name))
1984 				(*collisionCount)++;
1985 		}
1986 	}
1987 
1988 	// prompt user only if there is more than one collision, otherwise the
1989 	// single collision case will be handled as a "Prompt" case by CheckName
1990 	if (*collisionCount > 1) {
1991 		const char* verb = (moveMode == kMoveSelectionTo)
1992 			? B_TRANSLATE("moving")	: B_TRANSLATE("copying");
1993 		BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr));
1994 		replaceMsg.ReplaceAll("%verb", verb);
1995 
1996 		BAlert* alert = new BAlert("", replaceMsg.String(),
1997 			B_TRANSLATE("Cancel"), B_TRANSLATE("Prompt"),
1998 			B_TRANSLATE("Replace all"));
1999 		alert->SetShortcut(0, B_ESCAPE);
2000 		switch (alert->Go()) {
2001 			case 0:
2002 				return kCanceled;
2003 
2004 			case 1:
2005 				// user selected "Prompt"
2006 				return kPrompt;
2007 
2008 			case 2:
2009 				// user selected "Replace All"
2010 				return kReplaceAll;
2011 		}
2012 	}
2013 
2014 	return kNoConflicts;
2015 }
2016 
2017 
2018 void
2019 FileStatToString(StatStruct *stat, char *buffer, int32 length)
2020 {
2021 	tm timeData;
2022 	localtime_r(&stat->st_mtime, &timeData);
2023 
2024 
2025 	uint32 pos = sprintf(buffer,
2026 		B_TRANSLATE("\n\t(%Ld bytes, "), stat->st_size);
2027 	strftime(buffer + pos, length - pos,"%b %d %Y, %I:%M:%S %p)", &timeData);
2028 }
2029 
2030 
2031 status_t
2032 CheckName(uint32 moveMode, const BEntry *sourceEntry,
2033 	const BDirectory *destDir, bool multipleCollisions,
2034 	ConflictCheckResult &replaceAll)
2035 {
2036 	if (moveMode == kDuplicateSelection)
2037 		// when duplicating, we will never have a conflict
2038 		return B_OK;
2039 
2040 	// see if item already exists in destination dir
2041 	status_t err = B_OK;
2042 	char name[B_FILE_NAME_LENGTH];
2043 	sourceEntry->GetName(name);
2044 	bool sourceIsDirectory = sourceEntry->IsDirectory();
2045 
2046 	BDirectory srcDirectory;
2047 	if (sourceIsDirectory) {
2048 		srcDirectory.SetTo(sourceEntry);
2049 		BEntry destEntry;
2050 		destDir->GetEntry(&destEntry);
2051 
2052 		if (moveMode != kCreateLink
2053 			&& moveMode != kCreateRelativeLink
2054 			&& (srcDirectory == *destDir
2055 				|| srcDirectory.Contains(&destEntry))) {
2056 			(new BAlert("",
2057 				B_TRANSLATE("You can't move a folder into itself "
2058 				"or any of its own sub-folders."), B_TRANSLATE("OK"),
2059 				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2060 			return B_ERROR;
2061 		}
2062 	}
2063 
2064 	if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink
2065 		&& moveMode != kCreateRelativeLink) {
2066 		(new BAlert("",
2067 			B_TRANSLATE("You can't move or copy the trash."),
2068 			B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2069 		return B_ERROR;
2070 	}
2071 
2072 	BEntry entry;
2073 	if (destDir->FindEntry(name, &entry) != B_OK)
2074 		// no conflict, return
2075 		return B_OK;
2076 
2077 	if (moveMode == kCreateLink	|| moveMode == kCreateRelativeLink) {
2078 		// if we are creating link in the same directory, the conflict will
2079 		// be handled later by giving the link a unique name
2080 		sourceEntry->GetParent(&srcDirectory);
2081 
2082 		if (srcDirectory == *destDir)
2083 			return B_OK;
2084 	}
2085 
2086 	bool destIsDir = entry.IsDirectory();
2087 	// be sure not to replace the parent directory of the item being moved
2088 	if (destIsDir) {
2089 		BDirectory test_dir(&entry);
2090 		if (test_dir.Contains(sourceEntry)) {
2091 			(new BAlert("",
2092 				B_TRANSLATE("You can't replace a folder "
2093 				"with one of its sub-folders."),
2094 				B_TRANSLATE("OK"),
2095 				0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
2096 			return B_ERROR;
2097 		}
2098 	}
2099 
2100 	// Ensure user isn't trying to replace a file with folder or vice versa.
2101 	if (moveMode != kCreateLink
2102 		&& moveMode != kCreateRelativeLink
2103 		&& destIsDir != sourceIsDirectory) {
2104 			(new BAlert("", sourceIsDirectory
2105 				? B_TRANSLATE("You cannot replace a file with a folder or a "
2106 				"symbolic link.")
2107 				: B_TRANSLATE("You cannot replace a folder or a symbolic link "
2108 				"with a file."), B_TRANSLATE("OK"),	0, 0, B_WIDTH_AS_USUAL,
2109 				B_WARNING_ALERT))->Go();
2110 			return B_ERROR;
2111 		}
2112 
2113 	if (replaceAll != kReplaceAll) {
2114 		// prompt user to determine whether to replace or not
2115 
2116 		BString replaceMsg;
2117 
2118 		if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2119 			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr));
2120 			replaceMsg.ReplaceFirst("%name", name);
2121 		} else if (sourceEntry->IsDirectory()) {
2122 			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr));
2123 			replaceMsg.ReplaceFirst("%name", name);
2124 			replaceMsg.ReplaceFirst("%verb",
2125 				moveMode == kMoveSelectionTo
2126 				? B_TRANSLATE("moving")
2127 				: B_TRANSLATE("copying"));
2128 		} else {
2129 			char sourceBuffer[96], destBuffer[96];
2130 			StatStruct statBuffer;
2131 
2132 			if (!sourceEntry->IsDirectory() && sourceEntry->GetStat(
2133 				&statBuffer) == B_OK) {
2134 				FileStatToString(&statBuffer, sourceBuffer, 96);
2135 			} else
2136 				sourceBuffer[0] = '\0';
2137 
2138 			if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
2139 				FileStatToString(&statBuffer, destBuffer, 96);
2140 			else
2141 				destBuffer[0] = '\0';
2142 
2143 			replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr));
2144 			replaceMsg.ReplaceAll("%name", name);
2145 			replaceMsg.ReplaceFirst("%dest", destBuffer);
2146 			replaceMsg.ReplaceFirst("%src", sourceBuffer);
2147 			replaceMsg.ReplaceFirst("%movemode",
2148 				moveMode == kMoveSelectionTo
2149 				? B_TRANSLATE("moving")
2150 				: B_TRANSLATE("copying"));
2151 		}
2152 
2153 		// special case single collision (don't need Replace All shortcut)
2154 		BAlert *alert;
2155 		if (multipleCollisions || sourceIsDirectory) {
2156 			alert = new BAlert("", replaceMsg.String(),
2157 				B_TRANSLATE("Skip"), B_TRANSLATE("Replace all"));
2158 		} else {
2159 			alert = new BAlert("", replaceMsg.String(),
2160 				B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2161 			alert->SetShortcut(0, B_ESCAPE);
2162 		}
2163 		switch (alert->Go()) {
2164 			case 0:		// user selected "Cancel" or "Skip"
2165 				replaceAll = kCanceled;
2166 				return B_ERROR;
2167 
2168 			case 1:		// user selected "Replace" or "Replace All"
2169 				replaceAll = kReplaceAll;
2170 					// doesn't matter which since a single
2171 					// collision "Replace" is equivalent to a
2172 					// "Replace All"
2173 				break;
2174 		}
2175 	}
2176 
2177 	// delete destination item
2178 	if (!destIsDir)
2179 		err = entry.Remove();
2180 	else
2181 		return B_OK;
2182 
2183 	if (err != B_OK) {
2184 		BString error(B_TRANSLATE("There was a problem trying to replace \"%name\". The item might be open or busy."));
2185 		error.ReplaceFirst("%name", name);;
2186 		BAlert* alert = new BAlert("", error.String(),
2187 			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2188 		alert->SetShortcut(0, B_ESCAPE);
2189 		alert->Go();
2190 	}
2191 
2192 	return err;
2193 }
2194 
2195 
2196 status_t
2197 FSDeleteFolder(BEntry *dir_entry, CopyLoopControl *loopControl,
2198 	bool update_status, bool delete_top_dir, bool upateFileNameInStatus)
2199 {
2200 	entry_ref	ref;
2201 	BEntry		entry;
2202 	BDirectory	dir;
2203 	status_t	err;
2204 
2205 	dir.SetTo(dir_entry);
2206 	dir.Rewind();
2207 	// loop through everything in folder and delete it, skipping trouble files
2208 	for (;;) {
2209 		if (dir.GetNextEntry(&entry) != B_OK)
2210 			break;
2211 
2212 		entry.GetRef(&ref);
2213 
2214 		if (loopControl->CheckUserCanceled())
2215 			return kTrashCanceled;
2216 
2217 		if (entry.IsDirectory())
2218 			err = FSDeleteFolder(&entry, loopControl, update_status, true,
2219 				upateFileNameInStatus);
2220 		else {
2221 			err = entry.Remove();
2222 			if (update_status) {
2223 				loopControl->UpdateStatus(upateFileNameInStatus ? ref.name
2224 					: "", ref, 1, true);
2225 			}
2226 		}
2227 
2228 		if (err == kTrashCanceled)
2229 			return kTrashCanceled;
2230 		else if (err == B_OK)
2231 			dir.Rewind();
2232 		else {
2233 			loopControl->FileError(B_TRANSLATE_NOCOLLECT(
2234 					kFileDeleteErrorString), ref.name, err, false);
2235 		}
2236 	}
2237 
2238 	if (loopControl->CheckUserCanceled())
2239 		return kTrashCanceled;
2240 
2241 	dir_entry->GetRef(&ref);
2242 
2243 	if (update_status && delete_top_dir)
2244 		loopControl->UpdateStatus(NULL, ref, 1);
2245 
2246 	if (delete_top_dir)
2247 		return dir_entry->Remove();
2248 	else
2249 		return B_OK;
2250 }
2251 
2252 
2253 void
2254 FSMakeOriginalName(BString &string, const BDirectory *destDir,
2255 	const char *suffix)
2256 {
2257 	if (!destDir->Contains(string.String()))
2258 		return;
2259 
2260 	FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
2261 		const_cast<BDirectory *>(destDir), suffix ? suffix : " copy");
2262 	string.UnlockBuffer();
2263 }
2264 
2265 
2266 void
2267 FSMakeOriginalName(char *name, BDirectory *destDir, const char *suffix)
2268 {
2269 	char		root[B_FILE_NAME_LENGTH];
2270 	char		copybase[B_FILE_NAME_LENGTH];
2271 	char		temp_name[B_FILE_NAME_LENGTH + 10];
2272 	int32		fnum;
2273 
2274 	// is this name already original?
2275 	if (!destDir->Contains(name))
2276 		return;
2277 
2278 	// Determine if we're copying a 'copy'. This algorithm isn't perfect.
2279 	// If you're copying a file whose REAL name ends with 'copy' then
2280 	// this method will return "<filename> 1", not "<filename> copy"
2281 
2282 	// However, it will correctly handle file that contain 'copy'
2283 	// elsewhere in their name.
2284 
2285 	bool copycopy = false;		// are we copying a copy?
2286 	int32 len = (int32)strlen(name);
2287 	char *p = name + len - 1;	// get pointer to end os name
2288 
2289 	// eat up optional numbers (if were copying "<filename> copy 34")
2290 	while ((p > name) && isdigit(*p))
2291 		p--;
2292 
2293 	// eat up optional spaces
2294 	while ((p > name) && isspace(*p))
2295 		p--;
2296 
2297 	// now look for the phrase " copy"
2298 	if (p > name) {
2299 		// p points to the last char of the word. For example, 'y' in 'copy'
2300 
2301 		if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
2302 			// we found 'copy' in the right place.
2303 			// so truncate after 'copy'
2304 			*(p + 1) = '\0';
2305 			copycopy = true;
2306 
2307 			// save the 'root' name of the file, for possible later use.
2308 			// that is copy everything but trailing " copy". Need to
2309 			// NULL terminate after copy
2310 			strncpy(root, name, (uint32)((p - name) - 4));
2311 			root[(p - name) - 4] = '\0';
2312 		}
2313 	}
2314 
2315 	if (!copycopy) {
2316 		/*
2317 		 The name can't be longer than B_FILE_NAME_LENGTH.
2318 		 The algoritm adds " copy XX" to the name. That's 8 characters.
2319 		 B_FILE_NAME_LENGTH already accounts for NULL termination so we
2320 		 don't need to save an extra char at the end.
2321 		*/
2322 		if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
2323 			// name is too long - truncate it!
2324 			name[B_FILE_NAME_LENGTH - 8] = '\0';
2325 		}
2326 
2327 		strcpy(root, name);		// save root name
2328 		strcat(name, suffix);
2329 	}
2330 
2331 	strcpy(copybase, name);
2332 
2333 	// if name already exists then add a number
2334 	fnum = 1;
2335 	strcpy(temp_name, name);
2336 	while (destDir->Contains(temp_name)) {
2337 		sprintf(temp_name, "%s %ld", copybase, ++fnum);
2338 
2339 		if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) {
2340 			/*
2341 			 The name has grown too long. Maybe we just went from
2342 			 "<filename> copy 9" to "<filename> copy 10" and that extra
2343 			 character was too much. The solution is to further
2344 			 truncate the 'root' name and continue.
2345 			 ??? should we reset fnum or not ???
2346 			*/
2347 			root[strlen(root) - 1] = '\0';
2348 			sprintf(temp_name, "%s%s %ld", root, suffix, fnum);
2349 		}
2350 	}
2351 
2352 	ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1)));
2353 	strcpy(name, temp_name);
2354 }
2355 
2356 
2357 status_t
2358 FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl,
2359 	BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount)
2360 {
2361 	dir->Rewind();
2362 	BEntry entry;
2363 	while (dir->GetNextEntry(&entry) == B_OK) {
2364 		// be sure window hasn't closed
2365 		if (window && window->StopCalc())
2366 			return B_OK;
2367 
2368 		if (loopControl->CheckUserCanceled())
2369 			return kUserCanceled;
2370 
2371 		StatStruct statbuf;
2372 		status_t status = entry.GetStat(&statbuf);
2373 		if (status != B_OK)
2374 			return status;
2375 
2376 		(*_runningSize) += statbuf.st_blocks * 512;
2377 
2378 		if (S_ISDIR(statbuf.st_mode)) {
2379 			BDirectory subdir(&entry);
2380 			(*_dirCount)++;
2381 			status = FSRecursiveCalcSize(window, loopControl, &subdir,
2382 				_runningSize, _fileCount, _dirCount);
2383 			if (status != B_OK)
2384 				return status;
2385 		} else
2386 			(*_fileCount)++;
2387 	}
2388 	return B_OK;
2389 }
2390 
2391 
2392 status_t
2393 CalcItemsAndSize(CopyLoopControl* loopControl, BObjectList<entry_ref> *refList,
2394 	size_t blockSize, int32 *totalCount, off_t *totalSize)
2395 {
2396 	int32 fileCount = 0;
2397 	int32 dirCount = 0;
2398 
2399 	// check block size for sanity
2400 	if (blockSize < 0) {
2401 		// This would point at an error to retrieve the block size from
2402 		// the target volume. The code below cannot be used, it is only
2403 		// meant to get the block size when item operations happen on
2404 		// the source volume.
2405 		blockSize = 2048;
2406 	} else if (blockSize < 1024) {
2407 		blockSize = 1024;
2408 		if (entry_ref* ref = refList->ItemAt(0)) {
2409 			// TODO: This assumes all entries in the list share the same
2410 			// volume...
2411 			BVolume volume(ref->device);
2412 			if (volume.InitCheck() == B_OK)
2413 				blockSize = volume.BlockSize();
2414 		}
2415 	}
2416 	// File systems like ReiserFS may advertize a large block size, but
2417 	// stuff is still packed into blocks, so clamp maximum block size.
2418 	if (blockSize > 8192)
2419 		blockSize = 8192;
2420 
2421 	int32 num_items = refList->CountItems();
2422 	for (int32 i = 0; i < num_items; i++) {
2423 		entry_ref *ref = refList->ItemAt(i);
2424 		BEntry entry(ref);
2425 		StatStruct statbuf;
2426 		entry.GetStat(&statbuf);
2427 
2428 		if (loopControl->CheckUserCanceled())
2429 			return kUserCanceled;
2430 
2431 		if (S_ISDIR(statbuf.st_mode)) {
2432 			BDirectory dir(&entry);
2433 			dirCount++;
2434 			(*totalSize) += blockSize;
2435 			status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2436 				totalSize, &fileCount, &dirCount);
2437 			if (result != B_OK)
2438 				return result;
2439 		} else {
2440 			fileCount++;
2441 			(*totalSize) += statbuf.st_size + blockSize;
2442 		}
2443 	}
2444 
2445 	*totalCount += (fileCount + dirCount);
2446 	return B_OK;
2447 }
2448 
2449 
2450 status_t
2451 FSGetTrashDir(BDirectory *trashDir, dev_t dev)
2452 {
2453 	BVolume volume(dev);
2454 	status_t result = volume.InitCheck();
2455 	if (result != B_OK)
2456 		return result;
2457 
2458 	BPath path;
2459 	result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2460 	if (result != B_OK)
2461 		return result;
2462 
2463 	result = trashDir->SetTo(path.Path());
2464 	if (result == B_OK) {
2465 		// Directory already exists, we're done
2466 		return B_OK;
2467 	}
2468 
2469 	// The trash directory does not exist yet - change that!
2470 
2471 	result = create_directory(path.Path(), 0755);
2472 	if (result != B_OK)
2473 		return result;
2474 
2475 	result = trashDir->SetTo(path.Path());
2476 	if (result != B_OK)
2477 		return result;
2478 
2479 	// make trash invisible
2480 	StatStruct sbuf;
2481 	trashDir->GetStat(&sbuf);
2482 
2483 	PoseInfo poseInfo;
2484 	poseInfo.fInvisible = true;
2485 	poseInfo.fInitedDirectory = sbuf.st_ino;
2486 	trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2487 		sizeof(PoseInfo));
2488 
2489 	// set trash icon
2490 	size_t size;
2491 	const void* data
2492 		= GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2493 	if (data != NULL)
2494 		trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2495 
2496 	data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2497 	if (data != NULL)
2498 		trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2499 
2500 	data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, R_TrashIcon,
2501 		&size);
2502 	if (data != NULL)
2503 		trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2504 
2505 	return B_OK;
2506 }
2507 
2508 
2509 #if __GNUC__ && __GNUC__ < 3
2510 // obsolete version of FSGetDeskDir retained for bin compat with
2511 // BeIDE and a few other apps that apparently use it
2512 status_t
2513 FSGetDeskDir(BDirectory *deskDir, dev_t)
2514 {
2515 	// since we no longer keep a desktop directory on any volume other
2516 	// than /boot, redirect to FSGetDeskDir ignoring the volume argument
2517 	return FSGetDeskDir(deskDir);
2518 }
2519 #endif
2520 
2521 
2522 status_t
2523 FSGetDeskDir(BDirectory *deskDir)
2524 {
2525 	BPath path;
2526 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2527 	if (result != B_OK)
2528 		return result;
2529 
2530 	result = deskDir->SetTo(path.Path());
2531 	if (result != B_OK)
2532 		return result;
2533 
2534 	size_t size;
2535 	const void* data = GetTrackerResources()->
2536 		LoadResource('ICON', R_DeskIcon, &size);
2537 	if (data != NULL)
2538 		deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2539 
2540 	data = GetTrackerResources()->
2541 		LoadResource('MICN', R_DeskIcon, &size);
2542 	if (data != NULL)
2543 		deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2544 
2545 #ifdef __HAIKU__
2546 	data = GetTrackerResources()->
2547 		LoadResource(B_VECTOR_ICON_TYPE, R_DeskIcon, &size);
2548 	if (data != NULL)
2549 		deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2550 #endif
2551 
2552 	return B_OK;
2553 }
2554 
2555 
2556 status_t
2557 FSGetBootDeskDir(BDirectory *deskDir)
2558 {
2559 	BVolume	bootVol;
2560 	BVolumeRoster().GetBootVolume(&bootVol);
2561 	BPath path;
2562 
2563 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2564 		&bootVol);
2565 	if (result != B_OK)
2566 		return result;
2567 
2568 	return deskDir->SetTo(path.Path());
2569 }
2570 
2571 
2572 static bool
2573 FSIsDirFlavor(const BEntry *entry, directory_which directoryType)
2574 {
2575 	StatStruct dir_stat;
2576 	StatStruct entry_stat;
2577 	BVolume volume;
2578 	BPath path;
2579 
2580 	if (entry->GetStat(&entry_stat) != B_OK)
2581 		return false;
2582 
2583 	if (volume.SetTo(entry_stat.st_dev) != B_OK)
2584 		return false;
2585 
2586 	if (find_directory(directoryType, &path, false, &volume) != B_OK)
2587 		return false;
2588 
2589 	stat(path.Path(), &dir_stat);
2590 
2591 	return dir_stat.st_ino == entry_stat.st_ino
2592 		&& dir_stat.st_dev == entry_stat.st_dev;
2593 }
2594 
2595 
2596 bool
2597 FSIsPrintersDir(const BEntry *entry)
2598 {
2599 	return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2600 }
2601 
2602 
2603 bool
2604 FSIsTrashDir(const BEntry *entry)
2605 {
2606 	return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2607 }
2608 
2609 
2610 bool
2611 FSIsDeskDir(const BEntry *entry)
2612 {
2613 	BPath path;
2614 	status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2615 	if (result != B_OK)
2616 		return false;
2617 
2618 	BEntry entryToCompare(path.Path());
2619 	return entryToCompare == *entry;
2620 }
2621 
2622 
2623 bool
2624 FSIsHomeDir(const BEntry *entry)
2625 {
2626 	return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2627 }
2628 
2629 
2630 bool
2631 FSIsRootDir(const BEntry *entry)
2632 {
2633 	BPath path(entry);
2634 	return path == "/";
2635 }
2636 
2637 
2638 bool
2639 DirectoryMatchesOrContains(const BEntry *entry, directory_which which)
2640 {
2641 	BPath path;
2642 	if (find_directory(which, &path, false, NULL) != B_OK)
2643 		return false;
2644 
2645 	BEntry dirEntry(path.Path());
2646 	if (dirEntry.InitCheck() != B_OK)
2647 		return false;
2648 
2649 	if (dirEntry == *entry)
2650 		// root level match
2651 		return true;
2652 
2653 	BDirectory dir(&dirEntry);
2654 	return dir.Contains(entry);
2655 }
2656 
2657 
2658 bool
2659 DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath,
2660 	directory_which which)
2661 {
2662 	BPath path;
2663 	if (find_directory(which, &path, false, NULL) != B_OK)
2664 		return false;
2665 
2666 	path.Append(additionalPath);
2667 	BEntry dirEntry(path.Path());
2668 	if (dirEntry.InitCheck() != B_OK)
2669 		return false;
2670 
2671 	if (dirEntry == *entry)
2672 		// root level match
2673 		return true;
2674 
2675 	BDirectory dir(&dirEntry);
2676 	return dir.Contains(entry);
2677 }
2678 
2679 
2680 bool
2681 DirectoryMatches(const BEntry *entry, directory_which which)
2682 {
2683 	BPath path;
2684 	if (find_directory(which, &path, false, NULL) != B_OK)
2685 		return false;
2686 
2687 	BEntry dirEntry(path.Path());
2688 	if (dirEntry.InitCheck() != B_OK)
2689 		return false;
2690 
2691 	return dirEntry == *entry;
2692 }
2693 
2694 
2695 bool
2696 DirectoryMatches(const BEntry *entry, const char *additionalPath,
2697 	directory_which which)
2698 {
2699 	BPath path;
2700 	if (find_directory(which, &path, false, NULL) != B_OK)
2701 		return false;
2702 
2703 	path.Append(additionalPath);
2704 	BEntry dirEntry(path.Path());
2705 	if (dirEntry.InitCheck() != B_OK)
2706 		return false;
2707 
2708 	return dirEntry == *entry;
2709 }
2710 
2711 
2712 extern status_t
2713 FSFindTrackerSettingsDir(BPath *path, bool autoCreate)
2714 {
2715 	status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path,
2716 		autoCreate);
2717 	if (result != B_OK)
2718 		return result;
2719 
2720 	path->Append("Tracker");
2721 
2722 	return mkdir(path->Path(), 0777) ? B_OK : errno;
2723 }
2724 
2725 
2726 bool
2727 FSInTrashDir(const entry_ref *ref)
2728 {
2729 	BEntry entry(ref);
2730 	if (entry.InitCheck() != B_OK)
2731 		return false;
2732 
2733 	BDirectory trashDir;
2734 	if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2735 		return false;
2736 
2737 	return trashDir.Contains(&entry);
2738 }
2739 
2740 
2741 void
2742 FSEmptyTrash()
2743 {
2744 	if (find_thread("_tracker_empty_trash_") != B_OK) {
2745 		resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2746 			B_NORMAL_PRIORITY, NULL));
2747 	}
2748 }
2749 
2750 
2751 status_t
2752 empty_trash(void *)
2753 {
2754 	// empty trash on all mounted volumes
2755 	status_t err = B_OK;
2756 
2757 	TrackerCopyLoopControl loopControl(kTrashState);
2758 
2759 	// calculate the sum total of all items on all volumes in trash
2760 	BObjectList<entry_ref> srcList;
2761 	int32 totalCount = 0;
2762 	off_t totalSize = 0;
2763 
2764 	BVolumeRoster volumeRoster;
2765 	BVolume volume;
2766 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2767 		if (volume.IsReadOnly() || !volume.IsPersistent())
2768 			continue;
2769 
2770 		BDirectory trashDirectory;
2771 		if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2772 			continue;
2773 
2774 		BEntry entry;
2775 		trashDirectory.GetEntry(&entry);
2776 
2777 		entry_ref ref;
2778 		entry.GetRef(&ref);
2779 		srcList.AddItem(&ref);
2780 		err = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
2781 			&totalCount, &totalSize);
2782 		if (err != B_OK)
2783 			break;
2784 
2785 		srcList.MakeEmpty();
2786 
2787 		// don't count trash directory itself
2788 		totalCount--;
2789 	}
2790 
2791 	if (err == B_OK) {
2792 		loopControl.Init(totalCount, totalCount);
2793 
2794 		volumeRoster.Rewind();
2795 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2796 			if (volume.IsReadOnly() || !volume.IsPersistent())
2797 				continue;
2798 
2799 			BDirectory trashDirectory;
2800 			if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2801 				continue;
2802 
2803 			BEntry entry;
2804 			trashDirectory.GetEntry(&entry);
2805 			err = FSDeleteFolder(&entry, &loopControl, true, false);
2806 		}
2807 	}
2808 
2809 	if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) {
2810 		(new BAlert("",	B_TRANSLATE("Error emptying Trash!"),
2811 			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
2812 			B_WARNING_ALERT))->Go();
2813 	}
2814 
2815 	return B_OK;
2816 }
2817 
2818 
2819 status_t
2820 _DeleteTask(BObjectList<entry_ref> *list, bool confirm)
2821 {
2822 	if (confirm) {
2823 		bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
2824 
2825 		if (!dontMoveToTrash) {
2826 			BAlert* alert = new BAlert("",
2827 				B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2828 				B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
2829 				B_TRANSLATE("Delete"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2830 				B_WARNING_ALERT);
2831 
2832 			alert->SetShortcut(0, B_ESCAPE);
2833 			alert->SetShortcut(1, 'm');
2834 			alert->SetShortcut(2, 'd');
2835 
2836 			switch (alert->Go()) {
2837 				case 0:
2838 					delete list;
2839 					return B_OK;
2840 				case 1:
2841 					FSMoveToTrash(list, NULL, false);
2842 					return B_OK;
2843 			}
2844 		} else {
2845 			BAlert* alert = new BAlert("",
2846 				B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2847 				B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"), NULL,
2848 				B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
2849 
2850 			alert->SetShortcut(0, B_ESCAPE);
2851 			alert->SetShortcut(1, 'd');
2852 
2853 			if (!alert->Go()) {
2854 				delete list;
2855 				return B_OK;
2856 			}
2857 		}
2858 	}
2859 
2860 	TrackerCopyLoopControl loopControl(kDeleteState);
2861 
2862 	// calculate the sum total of all items on all volumes in trash
2863 	int32 totalItems = 0;
2864 	int64 totalSize = 0;
2865 
2866 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2867 		&totalSize);
2868 	if (err == B_OK) {
2869 		loopControl.Init(totalItems, totalItems);
2870 
2871 		int32 count = list->CountItems();
2872 		for (int32 index = 0; index < count; index++) {
2873 			entry_ref ref(*list->ItemAt(index));
2874 			BEntry entry(&ref);
2875 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2876 			if (entry.IsDirectory())
2877 				err = FSDeleteFolder(&entry, &loopControl, true, true, true);
2878 			else
2879 				err = entry.Remove();
2880 		}
2881 
2882 		if (err != kTrashCanceled && err != kUserCanceled && err != B_OK)
2883 			(new BAlert("",	B_TRANSLATE("Error deleting items"),
2884 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
2885 				B_WARNING_ALERT))->Go();
2886 	}
2887 
2888 	delete list;
2889 
2890 	return B_OK;
2891 }
2892 
2893 status_t
2894 FSRecursiveCreateFolder(BPath path)
2895 {
2896 	BEntry entry(path.Path());
2897 	if (entry.InitCheck() != B_OK) {
2898 		BPath parentPath;
2899 		status_t err = path.GetParent(&parentPath);
2900 		if (err != B_OK)
2901 			return err;
2902 
2903 		err = FSRecursiveCreateFolder(parentPath);
2904 		if (err != B_OK)
2905 			return err;
2906 	}
2907 
2908 	entry.SetTo(path.Path());
2909 	if (entry.Exists())
2910 		return B_FILE_EXISTS;
2911 	else {
2912 		char name[B_FILE_NAME_LENGTH];
2913 		BDirectory parent;
2914 
2915 		entry.GetParent(&parent);
2916 		entry.GetName(name);
2917 		parent.CreateDirectory(name, NULL);
2918 	}
2919 
2920 	return B_OK;
2921 }
2922 
2923 status_t
2924 _RestoreTask(BObjectList<entry_ref> *list)
2925 {
2926 	TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
2927 
2928 	// calculate the sum total of all items that will be restored
2929 	int32 totalItems = 0;
2930 	int64 totalSize = 0;
2931 
2932 	status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2933 		&totalSize);
2934 	if (err == B_OK) {
2935 		loopControl.Init(totalItems, totalItems);
2936 
2937 		int32 count = list->CountItems();
2938 		for (int32 index = 0; index < count; index++) {
2939 			entry_ref ref(*list->ItemAt(index));
2940 			BEntry entry(&ref);
2941 			BPath originalPath;
2942 
2943 			loopControl.UpdateStatus(ref.name, ref, 1, true);
2944 
2945 			if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
2946 				continue;
2947 
2948 			BEntry originalEntry(originalPath.Path());
2949 			BPath parentPath;
2950 			err = originalPath.GetParent(&parentPath);
2951 			if (err != B_OK)
2952 				continue;
2953 			BEntry parentEntry(parentPath.Path());
2954 
2955 			if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
2956 				if (FSRecursiveCreateFolder(parentPath) == B_OK) {
2957 					originalEntry.SetTo(originalPath.Path());
2958 					if (entry.InitCheck() != B_OK)
2959 						continue;
2960 				}
2961 			}
2962 
2963 			if (!originalEntry.Exists()) {
2964 				BDirectory dir(parentPath.Path());
2965 				if (dir.InitCheck() == B_OK) {
2966 					char leafName[B_FILE_NAME_LENGTH];
2967 					originalEntry.GetName(leafName);
2968 					if (entry.MoveTo(&dir, leafName) == B_OK) {
2969 						BNode node(&entry);
2970 						if (node.InitCheck() == B_OK)
2971 							node.RemoveAttr(kAttrOriginalPath);
2972 					}
2973 				}
2974 			}
2975 
2976 			err = loopControl.CheckUserCanceled();
2977 			if (err != B_OK)
2978 				break;
2979 		}
2980 	}
2981 
2982 	delete list;
2983 
2984 	return err;
2985 }
2986 
2987 void
2988 FSCreateTrashDirs()
2989 {
2990 	BVolume volume;
2991 	BVolumeRoster roster;
2992 
2993 	roster.Rewind();
2994 	while (roster.GetNextVolume(&volume) == B_OK) {
2995 		if (volume.IsReadOnly() || !volume.IsPersistent())
2996 			continue;
2997 
2998 		BDirectory trashDir;
2999 		FSGetTrashDir(&trashDir, volume.Device());
3000 	}
3001 }
3002 
3003 
3004 status_t
3005 FSCreateNewFolder(const entry_ref *ref)
3006 {
3007 	node_ref node;
3008 	node.device = ref->device;
3009 	node.node = ref->directory;
3010 
3011 	BDirectory dir(&node);
3012 	status_t result = dir.InitCheck();
3013 	if (result != B_OK)
3014 		return result;
3015 
3016 	// ToDo: is that really necessary here?
3017 	BString name(ref->name);
3018 	FSMakeOriginalName(name, &dir, "-");
3019 
3020 	BDirectory newDir;
3021 	result = dir.CreateDirectory(name.String(), &newDir);
3022 	if (result != B_OK)
3023 		return result;
3024 
3025 	BNodeInfo nodeInfo(&newDir);
3026 	nodeInfo.SetType(B_DIR_MIMETYPE);
3027 
3028 	return result;
3029 }
3030 
3031 
3032 status_t
3033 FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef,
3034 	node_ref *newNode)
3035 {
3036 	BDirectory dir(dirNode);
3037 	status_t result = dir.InitCheck();
3038 	if (result == B_OK) {
3039 		char name[B_FILE_NAME_LENGTH];
3040 		strlcpy(name, B_TRANSLATE("New folder"), sizeof(name));
3041 
3042 		int32 fnum = 1;
3043 		while (dir.Contains(name)) {
3044 			// if base name already exists then add a number
3045 			// ToDo:
3046 			// move this logic ot FSMakeOriginalName
3047 			if (++fnum > 9) {
3048 				snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"),
3049 					fnum);
3050 			} else {
3051 				snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"),
3052 					fnum);
3053 			}
3054 		}
3055 
3056 		BDirectory newDir;
3057 		result = dir.CreateDirectory(name, &newDir);
3058 		if (result == B_OK) {
3059 			BEntry entry;
3060 			newDir.GetEntry(&entry);
3061 			entry.GetRef(newRef);
3062 			entry.GetNodeRef(newNode);
3063 
3064 			BNodeInfo nodeInfo(&newDir);
3065 			nodeInfo.SetType(B_DIR_MIMETYPE);
3066 
3067 			// add undo item
3068 			NewFolderUndo undo(*newRef);
3069 			return B_OK;
3070 		}
3071 	}
3072 
3073 	BAlert* alert = new BAlert("",
3074 		B_TRANSLATE("Sorry, could not create a new folder."),
3075 		B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3076 	alert->SetShortcut(0, B_ESCAPE);
3077 	alert->Go();
3078 	return result;
3079 }
3080 
3081 
3082 ReadAttrResult
3083 ReadAttr(const BNode *node, const char *hostAttrName,
3084 	const char *foreignAttrName, type_code type, off_t offset, void *buffer,
3085 	size_t length, void (*swapFunc)(void *), bool isForeign)
3086 {
3087 	if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3088 			length) == (ssize_t)length) {
3089 		return kReadAttrNativeOK;
3090 	}
3091 
3092 	// PRINT(("trying %s\n", foreignAttrName));
3093 	// try the other endianness
3094 	if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3095 			!= (ssize_t)length) {
3096 		return kReadAttrFailed;
3097 	}
3098 
3099 	// PRINT(("got %s\n", foreignAttrName));
3100 	if (!swapFunc)
3101 		return kReadAttrForeignOK;
3102 
3103 	(swapFunc)(buffer);
3104 		// run the endian swapper
3105 
3106 	return kReadAttrForeignOK;
3107 }
3108 
3109 
3110 ReadAttrResult
3111 GetAttrInfo(const BNode *node, const char *hostAttrName,
3112 	const char *foreignAttrName, type_code *type, size_t *size)
3113 {
3114 	attr_info info;
3115 
3116 	if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3117 		if (type)
3118 			*type = info.type;
3119 		if (size)
3120 			*size = (size_t)info.size;
3121 
3122 		return kReadAttrNativeOK;
3123 	}
3124 
3125 	if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3126 		if (type)
3127 			*type = info.type;
3128 		if (size)
3129 			*size = (size_t)info.size;
3130 
3131 		return kReadAttrForeignOK;
3132 	}
3133 	return kReadAttrFailed;
3134 }
3135 
3136 // launching code
3137 
3138 static status_t
3139 TrackerOpenWith(const BMessage *refs)
3140 {
3141 	BMessage clone(*refs);
3142 	ASSERT(dynamic_cast<TTracker *>(be_app));
3143 	ASSERT(clone.what);
3144 	clone.AddInt32("launchUsingSelector", 0);
3145 	// runs the Open With window
3146 	be_app->PostMessage(&clone);
3147 
3148 	return B_OK;
3149 }
3150 
3151 
3152 static void
3153 AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on),
3154 	const entry_ref *appRef, const BMessage *refs, bool openWithOK)
3155 {
3156 	BMessage *task = new BMessage;
3157 	task->AddPointer("function", (void *)func);
3158 	task->AddMessage("refs", refs);
3159 	task->AddBool("openWithOK", openWithOK);
3160 	if (appRef != NULL)
3161 		task->AddRef("appRef", appRef);
3162 
3163 	extern BLooper *gLaunchLooper;
3164 	gLaunchLooper->PostMessage(task);
3165 }
3166 
3167 static bool
3168 SniffIfGeneric(const entry_ref *ref)
3169 {
3170 	BNode node(ref);
3171 	char type[B_MIME_TYPE_LENGTH];
3172 	BNodeInfo info(&node);
3173 	if (info.GetType(type) == B_OK
3174 		&& strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3175 		// already has a type and it's not octet stream
3176 		return false;
3177 	}
3178 
3179 	BPath path(ref);
3180 	if (path.Path()) {
3181 		// force a mimeset
3182 		node.RemoveAttr(kAttrMIMEType);
3183 		update_mime_info(path.Path(), 0, 1, 1);
3184 	}
3185 
3186 	return true;
3187 }
3188 
3189 static void
3190 SniffIfGeneric(const BMessage *refs)
3191 {
3192 	entry_ref ref;
3193 	for (int32 index = 0; ; index++) {
3194 		if (refs->FindRef("refs", index, &ref) != B_OK)
3195 			break;
3196 		SniffIfGeneric(&ref);
3197 	}
3198 }
3199 
3200 static void
3201 _TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs,
3202 	bool openWithOK)
3203 {
3204 	team_id team;
3205 
3206 	status_t error = B_ERROR;
3207 	BString alertString;
3208 
3209 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3210 		error = be_roster->Launch(appRef, refs, &team);
3211 		if (error == B_ALREADY_RUNNING)
3212 			// app already running, not really an error
3213 			error = B_OK;
3214 
3215 		if (error == B_OK)
3216 			break;
3217 
3218 		if (mimesetIt > 0)
3219 			break;
3220 
3221 		// failed to open, try mimesetting the refs and launching again
3222 		SniffIfGeneric(refs);
3223 	}
3224 
3225 	if (error == B_OK) {
3226 		// close possible parent window, if specified
3227 		const node_ref *nodeToClose = 0;
3228 		int32 numBytes;
3229 		refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3230 			(const void **)&nodeToClose, &numBytes);
3231 		if (nodeToClose)
3232 			dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose);
3233 	} else {
3234 		alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3235 		alertString.ReplaceFirst("%name", appRef->name);
3236 		alertString.ReplaceFirst("%error", strerror(error));
3237 		if (refs && openWithOK && error != B_SHUTTING_DOWN) {
3238 			alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3239 			BAlert* alert = new BAlert("", alertString.String(),
3240 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"),	0, B_WIDTH_AS_USUAL,
3241 				B_WARNING_ALERT);
3242 			alert->SetShortcut(0, B_ESCAPE);
3243 			if (alert->Go() == 1)
3244 				error = TrackerOpenWith(refs);
3245 		} else {
3246 			BAlert* alert = new BAlert("", alertString.String(),
3247 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3248 			alert->SetShortcut(0, B_ESCAPE);
3249 			alert->Go();
3250 		}
3251 	}
3252 }
3253 
3254 extern "C" char** environ;
3255 
3256 extern "C" status_t _kern_load_image(const char * const *flatArgs,
3257 	size_t flatArgsSize, int32 argCount, int32 envCount, int32 priority,
3258 	uint32 flags, port_id errorPort, uint32 errorToken);
3259 extern "C" status_t __flatten_process_args(const char * const *args,
3260 	int32 argCount, const char * const *env, int32 envCount, char ***_flatArgs,
3261 	size_t *_flatSize);
3262 
3263 
3264 static status_t
3265 LoaderErrorDetails(const entry_ref *app, BString &details)
3266 {
3267 	BPath path;
3268 	BEntry appEntry(app, true);
3269 
3270 	status_t result = appEntry.GetPath(&path);
3271 	if (result != B_OK)
3272 		return result;
3273 
3274 	char *argv[2] = { const_cast<char *>(path.Path()), 0};
3275 
3276 	port_id errorPort = create_port(1, "Tracker loader error");
3277 
3278 	// count environment variables
3279 	uint32 envCount = 0;
3280 	while (environ[envCount] != NULL)
3281 		envCount++;
3282 
3283 	char** flatArgs = NULL;
3284 	size_t flatArgsSize;
3285 	result = __flatten_process_args((const char **)argv, 1,
3286 		environ, envCount, &flatArgs, &flatArgsSize);
3287 	if (result != B_OK)
3288 		return result;
3289 
3290 	result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3291 		B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3292 	if (result == B_OK) {
3293 		// we weren't supposed to be able to start the application...
3294 		return B_ERROR;
3295 	}
3296 
3297 	// read error message from port and construct details string
3298 
3299 	ssize_t bufferSize;
3300 
3301 	do {
3302 		bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3303 	} while (bufferSize == B_INTERRUPTED);
3304 
3305 	if (bufferSize <= B_OK) {
3306 		delete_port(errorPort);
3307 		return bufferSize;
3308 	}
3309 
3310 	uint8 *buffer = (uint8 *)malloc(bufferSize);
3311 	if (buffer == NULL) {
3312 		delete_port(errorPort);
3313 		return B_NO_MEMORY;
3314 	}
3315 
3316 	bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3317 		B_RELATIVE_TIMEOUT, 0);
3318 	delete_port(errorPort);
3319 
3320 	if (bufferSize < B_OK) {
3321 		free(buffer);
3322 		return bufferSize;
3323 	}
3324 
3325 	BMessage message;
3326 	result = message.Unflatten((const char *)buffer);
3327 	free(buffer);
3328 
3329 	if (result != B_OK)
3330 		return result;
3331 
3332 	int32 errorCode = B_ERROR;
3333 	result = message.FindInt32("error", &errorCode);
3334 	if (result != B_OK)
3335 		return result;
3336 
3337 	const char *detailName = NULL;
3338 	switch (errorCode) {
3339 		case B_MISSING_LIBRARY:
3340 			detailName = "missing library";
3341 			break;
3342 
3343 		case B_MISSING_SYMBOL:
3344 			detailName = "missing symbol";
3345 			break;
3346 	}
3347 
3348 	if (detailName == NULL)
3349 		return B_ERROR;
3350 
3351 	const char *detail;
3352 	for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3353 			i++) {
3354 		if (i > 0)
3355 			details += ", ";
3356 		details += detail;
3357 	}
3358 
3359 	return B_OK;
3360 }
3361 
3362 
3363 static void
3364 _TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs,
3365 	bool openWithOK)
3366 {
3367 	BMessage copyOfRefs(*refs);
3368 
3369 	entry_ref documentRef;
3370 	if (copyOfRefs.FindRef("refs", &documentRef) != B_OK)
3371 		// nothing to launch, we are done
3372 		return;
3373 
3374 	status_t error = B_ERROR;
3375 	entry_ref app;
3376 	BMessage *refsToPass = NULL;
3377 	BString alertString;
3378 	const char *alternative = 0;
3379 
3380 	for (int32 mimesetIt = 0; ; mimesetIt++) {
3381 		alertString = "";
3382 		error = be_roster->FindApp(&documentRef, &app);
3383 
3384 		if (error != B_OK && mimesetIt == 0) {
3385 			SniffIfGeneric(&copyOfRefs);
3386 			continue;
3387 		}
3388 
3389 		if (error != B_OK) {
3390 			alertString.SetTo(B_TRANSLATE("Could not find an application to "
3391 				"open \"%name\" (%error). "));
3392 			alertString.ReplaceFirst("%name", documentRef.name);
3393 			alertString.ReplaceFirst("%error", strerror(error));
3394 			if (openWithOK)
3395 				alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3396 
3397 			break;
3398 		} else {
3399 			BEntry appEntry(&app, true);
3400 			for (int32 index = 0;;) {
3401 				// remove the app itself from the refs received so we don't try
3402 				// to open ourselves
3403 				entry_ref ref;
3404 				if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3405 					break;
3406 
3407 				// deal with symlinks properly
3408 				BEntry documentEntry(&ref, true);
3409 				if (appEntry == documentEntry) {
3410 					PRINT(("stripping %s, app %s \n", ref.name, app.name));
3411 					copyOfRefs.RemoveData("refs", index);
3412 				} else {
3413 					PRINT(("leaving %s, app %s  \n", ref.name, app.name));
3414 					index++;
3415 				}
3416 			}
3417 
3418 			refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3419 			team_id team;
3420 			error = be_roster->Launch(&app, refsToPass, &team);
3421 			if (error == B_ALREADY_RUNNING)
3422 				// app already running, not really an error
3423 				error = B_OK;
3424 			if (error == B_OK || mimesetIt != 0)
3425 				break;
3426 
3427 			SniffIfGeneric(&copyOfRefs);
3428 		}
3429 	}
3430 
3431 	if (error != B_OK && alertString.Length() == 0) {
3432 		BString loaderErrorString;
3433 		bool openedDocuments = true;
3434 
3435 		if (!refsToPass) {
3436 			// we just double clicked the app itself, do not offer to
3437 			// find a handling app
3438 			openWithOK = false;
3439 			openedDocuments = false;
3440 		}
3441 
3442 		if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3443 			alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3444 				"The file is mistakenly marked as executable. "));
3445 			alertString.ReplaceFirst("%name", app.name);
3446 
3447 			if (!openWithOK) {
3448 				// offer the possibility to change the permissions
3449 
3450 				alertString << B_TRANSLATE("\nShould this be fixed?");
3451 				BAlert* alert = new BAlert("", alertString.String(),
3452 					B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3453 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3454 				alert->SetShortcut(0, B_ESCAPE);
3455 				if (alert->Go() == 1) {
3456 					BEntry entry(&documentRef);
3457 					mode_t permissions;
3458 
3459 					error = entry.GetPermissions(&permissions);
3460 					if (error == B_OK) {
3461 						error = entry.SetPermissions(permissions
3462 							& ~(S_IXUSR | S_IXGRP | S_IXOTH));
3463 					}
3464 					if (error == B_OK) {
3465 						// we updated the permissions, so let's try again
3466 						_TrackerLaunchDocuments(NULL, refs, false);
3467 						return;
3468 					} else {
3469 						alertString.SetTo(B_TRANSLATE("Could not update "
3470 							"permissions of file \"%name\". %error"));
3471 						alertString.ReplaceFirst("%name", app.name);
3472 						alertString.ReplaceFirst("%error", strerror(error));
3473 					}
3474 				} else
3475 					return;
3476 			}
3477 
3478 			alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3479 		} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3480 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3481 				"because application \"%app\" is in the Trash. "));
3482 			alertString.ReplaceFirst("%document", documentRef.name);
3483 			alertString.ReplaceFirst("%app", app.name);
3484 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3485 		} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3486 			alertString.SetTo(
3487 				B_TRANSLATE("Could not open \"%name\" (%error). "));
3488 			alertString.ReplaceFirst("%name", documentRef.name);
3489 			alertString.ReplaceFirst("%error", strerror(error));
3490 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3491 		} else if (error == B_MISSING_SYMBOL
3492 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3493 			if (openedDocuments) {
3494 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3495 					"with application \"%app\" (Missing symbol: %symbol). \n"));
3496 				alertString.ReplaceFirst("%document", documentRef.name);
3497 				alertString.ReplaceFirst("%app", app.name);
3498 				alertString.ReplaceFirst("%symbol", loaderErrorString.String());
3499 			} else {
3500 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3501 					"(Missing symbol: %symbol). \n"));
3502 				alertString.ReplaceFirst("%document", documentRef.name);
3503 				alertString.ReplaceFirst("%symbol", loaderErrorString.String());
3504 			}
3505 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3506 		} else if (error == B_MISSING_LIBRARY
3507 			&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3508 			if (openedDocuments) {
3509 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3510 					"with application \"%app\" (Missing libraries: %library). "
3511 					"\n"));
3512 				alertString.ReplaceFirst("%document", documentRef.name);
3513 				alertString.ReplaceFirst("%app", app.name);
3514 				alertString.ReplaceFirst("%library", loaderErrorString.String());
3515 			} else {
3516 				alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3517 					"(Missing libraries: %library). \n"));
3518 				alertString.ReplaceFirst("%document", documentRef.name);
3519 				alertString.ReplaceFirst("%library", loaderErrorString.String());
3520 			}
3521 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3522 		} else {
3523 			alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3524 				"application \"%app\" (%error). "));
3525 				alertString.ReplaceFirst("%document", documentRef.name);
3526 				alertString.ReplaceFirst("%app", app.name);
3527 				alertString.ReplaceFirst("%error", strerror(error));
3528 			alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3529 		}
3530 	}
3531 
3532 	if (error != B_OK) {
3533 		if (openWithOK) {
3534 			ASSERT(alternative);
3535 			alertString << alternative;
3536 			BAlert* alert = new BAlert("", alertString.String(),
3537 				B_TRANSLATE("Cancel"), B_TRANSLATE("Find"),	0, B_WIDTH_AS_USUAL,
3538 				B_WARNING_ALERT);
3539 			alert->SetShortcut(0, B_ESCAPE);
3540 			if (alert->Go() == 1)
3541 				error = TrackerOpenWith(refs);
3542 		} else {
3543 			BAlert* alert = new BAlert("", alertString.String(),
3544 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3545 			alert->SetShortcut(0, B_ESCAPE);
3546 			alert->Go();
3547 		}
3548 	}
3549 }
3550 
3551 // the following three calls don't return any reasonable error codes,
3552 // should fix that, making them void
3553 
3554 status_t
3555 TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async,
3556 	bool openWithOK)
3557 {
3558 	if (!async)
3559 		_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3560 	else {
3561 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3562 			openWithOK);
3563 	}
3564 
3565 	return B_OK;
3566 }
3567 
3568 status_t
3569 TrackerLaunch(const entry_ref *appRef, bool async)
3570 {
3571 	if (!async)
3572 		_TrackerLaunchAppWithDocuments(appRef, 0, false);
3573 	else
3574 		AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3575 
3576 	return B_OK;
3577 }
3578 
3579 status_t
3580 TrackerLaunch(const BMessage *refs, bool async, bool openWithOK)
3581 {
3582 	if (!async)
3583 		_TrackerLaunchDocuments(0, refs, openWithOK);
3584 	else
3585 		AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK);
3586 
3587 	return B_OK;
3588 }
3589 
3590 status_t
3591 LaunchBrokenLink(const char *signature, const BMessage *refs)
3592 {
3593 	// This call is to support a hacky workaround for double-clicking
3594 	// broken refs for cifs
3595 	be_roster->Launch(signature, const_cast<BMessage *>(refs));
3596 	return B_OK;
3597 }
3598 
3599 // external launch calls; need to be robust, work if Tracker is not running
3600 
3601 #if !B_BEOS_VERSION_DANO
3602 _IMPEXP_TRACKER
3603 #endif
3604 status_t
3605 FSLaunchItem(const entry_ref *application, const BMessage *refsReceived,
3606 	bool async, bool openWithOK)
3607 {
3608 	return TrackerLaunch(application, refsReceived, async, openWithOK);
3609 }
3610 
3611 
3612 #if !B_BEOS_VERSION_DANO
3613 _IMPEXP_TRACKER
3614 #endif
3615 status_t
3616 FSOpenWith(BMessage *listOfRefs)
3617 {
3618 	status_t result = B_ERROR;
3619 	listOfRefs->what = B_REFS_RECEIVED;
3620 
3621 	if (dynamic_cast<TTracker *>(be_app))
3622 		result = TrackerOpenWith(listOfRefs);
3623 	else
3624 		ASSERT(!"not yet implemented");
3625 
3626 	return result;
3627 }
3628 
3629 // legacy calls, need for compatibility
3630 
3631 void
3632 FSOpenWithDocuments(const entry_ref *executable, BMessage *documents)
3633 {
3634 	TrackerLaunch(executable, documents, true);
3635 	delete documents;
3636 }
3637 
3638 status_t
3639 FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs)
3640 {
3641 	BMessage temp(B_REFS_RECEIVED);
3642 	if (!listOfRefs) {
3643 		ASSERT(ref);
3644 		temp.AddRef("refs", ref);
3645 		listOfRefs = &temp;
3646 	}
3647 	FSOpenWith(listOfRefs);
3648 	return B_OK;
3649 }
3650 
3651 status_t
3652 FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async)
3653 {
3654 	if (message)
3655 		message->what = B_REFS_RECEIVED;
3656 
3657 	status_t result = TrackerLaunch(ref, message, async, true);
3658 	delete message;
3659 	return result;
3660 }
3661 
3662 
3663 void
3664 FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace)
3665 {
3666 	FSLaunchItem(ref, message, workspace, true);
3667 }
3668 
3669 // Get the original path of an entry in the trash
3670 status_t
3671 FSGetOriginalPath(BEntry *entry, BPath *result)
3672 {
3673 	status_t err;
3674 	entry_ref ref;
3675 	err = entry->GetRef(&ref);
3676 	if (err != B_OK)
3677 		return err;
3678 
3679 	// Only call the routine for entries in the trash
3680 	if (!FSInTrashDir(&ref))
3681 		return B_ERROR;
3682 
3683 	BNode node(entry);
3684 	BString originalPath;
3685 	if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3686 		// We're in luck, the entry has the original path in an attribute
3687 		err = result->SetTo(originalPath.String());
3688 		return err;
3689 	}
3690 
3691 	// Iterate the parent directories to find one with
3692 	// the original path attribute
3693 	BEntry parent(*entry);
3694 	err = parent.InitCheck();
3695 	if (err != B_OK)
3696 		return err;
3697 
3698 	// walk up the directory structure until we find a node
3699 	// with original path attribute
3700 	do {
3701 		// move to the parent of this node
3702 		err = parent.GetParent(&parent);
3703 		if (err != B_OK)
3704 			return err;
3705 
3706 		// return if we are at the root of the trash
3707 		if (FSIsTrashDir(&parent))
3708 			return B_ENTRY_NOT_FOUND;
3709 
3710 		// get the parent as a node
3711 		err = node.SetTo(&parent);
3712 		if (err != B_OK)
3713 			return err;
3714 	} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3715 
3716 	// Found the attribute, figure out there this file
3717 	// used to live, based on the successfully-read attribute
3718 	err = result->SetTo(originalPath.String());
3719 	if (err != B_OK)
3720 		return err;
3721 
3722 	BPath path, pathParent;
3723 	err = parent.GetPath(&pathParent);
3724 	if (err != B_OK)
3725 		return err;
3726 	err = entry->GetPath(&path);
3727 	if (err != B_OK)
3728 		return err;
3729 	result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3730 		// compute the new path by appending the offset of
3731 		// the item we are locating, to the original path
3732 		// of the parent
3733 	return B_OK;
3734 }
3735 
3736 directory_which
3737 WellKnowEntryList::Match(const node_ref *node)
3738 {
3739 	const WellKnownEntry *result = MatchEntry(node);
3740 	if (result)
3741 		return result->which;
3742 
3743 	return (directory_which)-1;
3744 }
3745 
3746 const WellKnowEntryList::WellKnownEntry *
3747 WellKnowEntryList::MatchEntry(const node_ref *node)
3748 {
3749 	if (!self)
3750 		self = new WellKnowEntryList();
3751 
3752 	return self->MatchEntryCommon(node);
3753 }
3754 
3755 const WellKnowEntryList::WellKnownEntry *
3756 WellKnowEntryList::MatchEntryCommon(const node_ref *node)
3757 {
3758 	uint32 count = entries.size();
3759 	for (uint32 index = 0; index < count; index++)
3760 		if (*node == entries[index].node)
3761 			return &entries[index];
3762 
3763 	return NULL;
3764 }
3765 
3766 
3767 void
3768 WellKnowEntryList::Quit()
3769 {
3770 	delete self;
3771 	self = NULL;
3772 }
3773 
3774 
3775 void
3776 WellKnowEntryList::AddOne(directory_which which, const char *name)
3777 {
3778 	BPath path;
3779 	if (find_directory(which, &path, true) != B_OK)
3780 		return;
3781 
3782 	BEntry entry(path.Path(), true);
3783 	node_ref node;
3784 	if (entry.GetNodeRef(&node) != B_OK)
3785 		return;
3786 
3787 	entries.push_back(WellKnownEntry(&node, which, name));
3788 }
3789 
3790 
3791 void
3792 WellKnowEntryList::AddOne(directory_which which, directory_which base,
3793 	const char *extra, const char *name)
3794 {
3795 	BPath path;
3796 	if (find_directory(base, &path, true) != B_OK)
3797 		return;
3798 
3799 	path.Append(extra);
3800 	BEntry entry(path.Path(), true);
3801 	node_ref node;
3802 	if (entry.GetNodeRef(&node) != B_OK)
3803 		return;
3804 
3805 	entries.push_back(WellKnownEntry(&node, which, name));
3806 }
3807 
3808 
3809 void
3810 WellKnowEntryList::AddOne(directory_which which, const char *path,
3811 	const char *name)
3812 {
3813 	BEntry entry(path, true);
3814 	node_ref node;
3815 	if (entry.GetNodeRef(&node) != B_OK)
3816 		return;
3817 
3818 	entries.push_back(WellKnownEntry(&node, which, name));
3819 }
3820 
3821 
3822 WellKnowEntryList::WellKnowEntryList()
3823 {
3824 #ifdef __HAIKU__
3825 	AddOne(B_SYSTEM_DIRECTORY, "system");
3826 #else
3827 	AddOne(B_BEOS_DIRECTORY, "beos");
3828 	AddOne(B_BEOS_SYSTEM_DIRECTORY, "system");
3829 #endif
3830 	AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
3831 	AddOne(B_USER_DIRECTORY, "home");
3832 
3833 	AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
3834 	AddOne(B_COMMON_FONTS_DIRECTORY, "fonts");
3835 	AddOne(B_USER_FONTS_DIRECTORY, "fonts");
3836 
3837 	AddOne(B_BEOS_APPS_DIRECTORY, "apps");
3838 	AddOne(B_APPS_DIRECTORY, "apps");
3839 	AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
3840 		B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
3841 
3842 	AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
3843 	AddOne(B_PREFERENCES_DIRECTORY, "preferences");
3844 	AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
3845 		B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
3846 
3847 	AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
3848 		"mail");
3849 
3850 	AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
3851 		"queries", "queries");
3852 
3853 
3854 
3855 	AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop");
3856 	AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
3857 		B_USER_DESKBAR_DIRECTORY, "Development", "develop");
3858 
3859 	AddOne(B_USER_CONFIG_DIRECTORY, "config");
3860 
3861 	AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
3862 		"people", "people");
3863 
3864 	AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
3865 		"downloads", "downloads");
3866 }
3867 
3868 WellKnowEntryList *WellKnowEntryList::self = NULL;
3869 
3870 } // namespace BPrivate
3871