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