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