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