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