RemoteDrawingEngine: Reduce RP_READ_BITMAP result timeout.
[haiku.git] / src / kits / tracker / FSUtils.cpp
blob5399efcd3369965de075d367276bf8680565f165
1 /*
2 Open Tracker License
4 Terms and Conditions
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
35 // Tracker file system calls.
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
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.
48 #include <ctype.h>
49 #include <errno.h>
50 #include <strings.h>
51 #include <unistd.h>
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 <MessageFormat.h>
62 #include <NodeInfo.h>
63 #include <Path.h>
64 #include <Roster.h>
65 #include <Screen.h>
66 #include <String.h>
67 #include <SymLink.h>
68 #include <Volume.h>
69 #include <VolumeRoster.h>
71 #include <fs_attr.h>
72 #include <fs_info.h>
73 #include <sys/utsname.h>
75 #include <AutoLocker.h>
76 #include <libroot/libroot_private.h>
77 #include <system/syscalls.h>
79 #include "Attributes.h"
80 #include "Bitmaps.h"
81 #include "Commands.h"
82 #include "FSUndoRedo.h"
83 #include "FSUtils.h"
84 #include "InfoWindow.h"
85 #include "MimeTypes.h"
86 #include "OverrideAlert.h"
87 #include "StatusWindow.h"
88 #include "Thread.h"
89 #include "Tracker.h"
90 #include "TrackerSettings.h"
91 #include "Utilities.h"
92 #include "VirtualDirectoryManager.h"
95 enum {
96 kUserCanceled = B_ERRORS_END + 1,
97 kCopyCanceled = kUserCanceled,
98 kTrashCanceled
101 enum ConflictCheckResult {
102 kCanceled = kUserCanceled,
103 kPrompt,
104 kSkipAll,
105 kReplace,
106 kReplaceAll,
107 kNoConflicts
111 namespace BPrivate {
113 #undef B_TRANSLATION_CONTEXT
114 #define B_TRANSLATION_CONTEXT "FSUtils"
116 static status_t FSDeleteFolder(BEntry*, CopyLoopControl*, bool updateStatus,
117 bool deleteTopDir = true, bool upateFileNameInStatus = false);
118 static status_t MoveEntryToTrash(BEntry*, BPoint*, Undo &undo);
119 static void LowLevelCopy(BEntry*, StatStruct*, BDirectory*, char* destName,
120 CopyLoopControl*, BPoint*);
121 status_t DuplicateTask(BObjectList<entry_ref>* srcList);
122 static status_t MoveTask(BObjectList<entry_ref>*, BEntry*, BList*, uint32);
123 static status_t _DeleteTask(BObjectList<entry_ref>*, bool);
124 static status_t _RestoreTask(BObjectList<entry_ref>*);
125 status_t CalcItemsAndSize(CopyLoopControl* loopControl,
126 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
127 off_t* totalSize);
128 status_t MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc,
129 uint32 moveMode, const char* newName, Undo &undo,
130 CopyLoopControl* loopControl);
131 ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref>* srcList,
132 const BDirectory* destDir, int32* collisionCount, uint32 moveMode);
133 status_t CheckName(uint32 moveMode, const BEntry* srcEntry,
134 const BDirectory* destDir, bool multipleCollisions,
135 ConflictCheckResult &);
136 void CopyAttributes(CopyLoopControl* control, BNode* srcNode,
137 BNode* destNode, void* buffer, size_t bufsize);
138 void CopyPoseLocation(BNode* src, BNode* dest);
139 bool DirectoryMatchesOrContains(const BEntry*, directory_which);
140 bool DirectoryMatchesOrContains(const BEntry*, const char* additionalPath,
141 directory_which);
142 bool DirectoryMatches(const BEntry*, directory_which);
143 bool DirectoryMatches(const BEntry*, const char* additionalPath,
144 directory_which);
146 status_t empty_trash(void*);
149 static const char* kDeleteConfirmationStr =
150 B_TRANSLATE_MARK("Are you sure you want to delete the "
151 "selected item(s)? This operation cannot be reverted.");
153 static const char* kReplaceStr =
154 B_TRANSLATE_MARK("You are trying to replace the item:\n"
155 "\t%name%dest\n"
156 "with:\n"
157 "\t%name%src\n\n"
158 "Would you like to replace it with the one you are %movemode?");
160 static const char* kDirectoryReplaceStr =
161 B_TRANSLATE_MARK("An item named \"%name\" already exists in "
162 "this folder, and may contain\nitems with the same names. Would you like "
163 "to replace them with those contained in the folder you are %verb?");
165 static const char* kSymLinkReplaceStr =
166 B_TRANSLATE_MARK("An item named \"%name\" already exists in this "
167 "folder. Would you like to replace it with the symbolic link you are "
168 "creating?");
170 static const char* kNoFreeSpace =
171 B_TRANSLATE_MARK("Sorry, there is not enough free space on the "
172 "destination volume to copy the selection.");
174 static const char* kFileErrorString =
175 B_TRANSLATE_MARK("Error copying file \"%name\":\n\t%error\n\n"
176 "Would you like to continue?");
178 static const char* kFolderErrorString =
179 B_TRANSLATE_MARK("Error copying folder \"%name\":\n\t%error\n\n"
180 "Would you like to continue?");
182 static const char* kFileDeleteErrorString =
183 B_TRANSLATE_MARK("There was an error deleting \"%name\""
184 ":\n\t%error");
186 static const char* kReplaceManyStr =
187 B_TRANSLATE_MARK("Some items already exist in this folder with "
188 "the same names as the items you are %verb.\n \nWould you like to "
189 "replace them with the ones you are %verb or be prompted for each "
190 "one?");
192 static const char* kFindAlternativeStr =
193 B_TRANSLATE_MARK("Would you like to find some other suitable "
194 "application?");
196 static const char* kFindApplicationStr =
197 B_TRANSLATE_MARK("Would you like to find a suitable application "
198 "to open the file?");
201 // Skip these attributes when copying in Tracker
202 const char* kSkipAttributes[] = {
203 kAttrPoseInfo,
204 NULL
208 // #pragma mark - CopyLoopControl
211 CopyLoopControl::~CopyLoopControl()
216 void
217 CopyLoopControl::Init(uint32 jobKind)
222 void
223 CopyLoopControl::Init(int32 totalItems, off_t totalSize,
224 const entry_ref* destDir, bool showCount)
229 bool
230 CopyLoopControl::FileError(const char* message, const char* name,
231 status_t error, bool allowContinue)
233 return false;
237 void
238 CopyLoopControl::UpdateStatus(const char* name, const entry_ref& ref,
239 int32 count, bool optional)
244 bool
245 CopyLoopControl::CheckUserCanceled()
247 return false;
251 CopyLoopControl::OverwriteMode
252 CopyLoopControl::OverwriteOnConflict(const BEntry* srcEntry,
253 const char* destName, const BDirectory* destDir, bool srcIsDir,
254 bool dstIsDir)
256 return kReplace;
260 bool
261 CopyLoopControl::SkipEntry(const BEntry*, bool)
263 // Tracker makes no exceptions
264 return false;
268 void
269 CopyLoopControl::ChecksumChunk(const char*, size_t)
274 bool
275 CopyLoopControl::ChecksumFile(const entry_ref*)
277 return true;
281 bool
282 CopyLoopControl::SkipAttribute(const char*)
284 return false;
288 bool
289 CopyLoopControl::PreserveAttribute(const char*)
291 return false;
295 // #pragma mark - TrackerCopyLoopControl
298 TrackerCopyLoopControl::TrackerCopyLoopControl()
300 fThread(find_thread(NULL)),
301 fSourceList(NULL)
306 TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind)
308 fThread(find_thread(NULL)),
309 fSourceList(NULL)
311 Init(jobKind);
315 TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems,
316 off_t totalSize)
318 fThread(find_thread(NULL)),
319 fSourceList(NULL)
321 Init(totalItems, totalSize);
325 TrackerCopyLoopControl::~TrackerCopyLoopControl()
327 if (gStatusWindow != NULL)
328 gStatusWindow->RemoveStatusItem(fThread);
332 void
333 TrackerCopyLoopControl::Init(uint32 jobKind)
335 if (gStatusWindow != NULL)
336 gStatusWindow->CreateStatusItem(fThread, (StatusWindowState)jobKind);
340 void
341 TrackerCopyLoopControl::Init(int32 totalItems, off_t totalSize,
342 const entry_ref* destDir, bool showCount)
344 if (gStatusWindow != NULL) {
345 gStatusWindow->InitStatusItem(fThread, totalItems, totalSize,
346 destDir, showCount);
351 bool
352 TrackerCopyLoopControl::FileError(const char* message, const char* name,
353 status_t error, bool allowContinue)
355 BString buffer(message);
356 buffer.ReplaceFirst("%name", name);
357 buffer.ReplaceFirst("%error", strerror(error));
359 if (allowContinue) {
360 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
361 B_TRANSLATE("OK"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
362 alert->SetShortcut(0, B_ESCAPE);
363 return alert->Go() != 0;
366 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 0, 0,
367 B_WIDTH_AS_USUAL, B_STOP_ALERT);
368 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
369 alert->Go();
370 return false;
374 void
375 TrackerCopyLoopControl::UpdateStatus(const char* name, const entry_ref&,
376 int32 count, bool optional)
378 if (gStatusWindow != NULL)
379 gStatusWindow->UpdateStatus(fThread, name, count, optional);
383 bool
384 TrackerCopyLoopControl::CheckUserCanceled()
386 if (gStatusWindow == NULL)
387 return false;
389 if (gStatusWindow->CheckCanceledOrPaused(fThread))
390 return true;
392 if (fSourceList != NULL) {
393 // TODO: Check if the user dropped additional files onto this job.
394 // printf("%p->CheckUserCanceled()\n", this);
397 return false;
401 bool
402 TrackerCopyLoopControl::SkipAttribute(const char* attributeName)
404 for (const char** skipAttribute = kSkipAttributes; *skipAttribute;
405 skipAttribute++) {
406 if (strcmp(*skipAttribute, attributeName) == 0)
407 return true;
410 return false;
414 void
415 TrackerCopyLoopControl::SetSourceList(EntryList* list)
417 fSourceList = list;
421 // #pragma mark - the rest
424 static BNode*
425 GetWritableNode(BEntry* entry, StatStruct* statBuf = 0)
427 // utility call that works around the problem with BNodes not being
428 // universally writeable
429 // BNodes created on files will fail to WriteAttr because they do not
430 // have the right r/w permissions
432 StatStruct localStatbuf;
434 if (!statBuf) {
435 statBuf = &localStatbuf;
436 if (entry->GetStat(statBuf) != B_OK)
437 return 0;
440 if (S_ISREG(statBuf->st_mode))
441 return new BFile(entry, O_RDWR);
443 return new BNode(entry);
447 bool
448 CheckDevicesEqual(const entry_ref* srcRef, const Model* targetModel)
450 BDirectory destDir (targetModel->EntryRef());
451 struct stat deststat;
452 destDir.GetStat(&deststat);
454 return srcRef->device == deststat.st_dev;
458 status_t
459 FSSetPoseLocation(ino_t destDirInode, BNode* destNode, BPoint point)
461 PoseInfo poseInfo;
462 poseInfo.fInvisible = false;
463 poseInfo.fInitedDirectory = destDirInode;
464 poseInfo.fLocation = point;
466 status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
467 &poseInfo, sizeof(poseInfo));
469 if (result == sizeof(poseInfo))
470 return B_OK;
472 return result;
476 status_t
477 FSSetPoseLocation(BEntry* entry, BPoint point)
479 BNode node(entry);
480 status_t result = node.InitCheck();
481 if (result != B_OK)
482 return result;
484 BDirectory parent;
485 result = entry->GetParent(&parent);
486 if (result != B_OK)
487 return result;
489 node_ref destNodeRef;
490 result = parent.GetNodeRef(&destNodeRef);
491 if (result != B_OK)
492 return result;
494 return FSSetPoseLocation(destNodeRef.node, &node, point);
498 bool
499 FSGetPoseLocation(const BNode* node, BPoint* point)
501 PoseInfo poseInfo;
502 if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign,
503 B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap)
504 == kReadAttrFailed) {
505 return false;
508 if (poseInfo.fInitedDirectory == -1LL)
509 return false;
511 *point = poseInfo.fLocation;
513 return true;
517 static void
518 SetUpPoseLocation(ino_t sourceParentIno, ino_t destParentIno,
519 const BNode* sourceNode, BNode* destNode, BPoint* loc)
521 BPoint point;
522 if (loc == NULL
523 // we don't have a position yet
524 && sourceParentIno != destParentIno
525 // we aren't copying into the same directory
526 && FSGetPoseLocation(sourceNode, &point)) {
527 // the original has a valid inited location
528 loc = &point;
529 // copy the originals location
532 if (loc != NULL && loc != (BPoint*)-1) {
533 // loc of -1 is used when copying/moving into a window in list mode
534 // where copying positions would not work
535 // ToSo:
536 // should push all this logic to upper levels
537 FSSetPoseLocation(destParentIno, destNode, *loc);
542 void
543 FSMoveToFolder(BObjectList<entry_ref>* srcList, BEntry* destEntry,
544 uint32 moveMode, BList* pointList)
546 if (srcList->IsEmpty()) {
547 delete srcList;
548 delete pointList;
549 delete destEntry;
550 return;
553 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
554 destEntry, pointList, moveMode);
558 void
559 FSDelete(entry_ref* ref, bool async, bool confirm)
561 BObjectList<entry_ref>* list = new BObjectList<entry_ref>(1, true);
562 list->AddItem(ref);
563 FSDeleteRefList(list, async, confirm);
567 void
568 FSDeleteRefList(BObjectList<entry_ref>* list, bool async, bool confirm)
570 if (async) {
571 LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list,
572 confirm);
573 } else
574 _DeleteTask(list, confirm);
578 void
579 FSRestoreRefList(BObjectList<entry_ref>* list, bool async)
581 if (async) {
582 LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask,
583 list);
584 } else
585 _RestoreTask(list);
589 void
590 FSMoveToTrash(BObjectList<entry_ref>* srcList, BList* pointList, bool async)
592 if (srcList->IsEmpty()) {
593 delete srcList;
594 delete pointList;
595 return;
598 if (async)
599 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
600 (BEntry*)0, pointList, kMoveSelectionTo);
601 else
602 MoveTask(srcList, 0, pointList, kMoveSelectionTo);
606 static bool
607 IsDisksWindowIcon(BEntry* entry)
609 BPath path;
610 if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
611 return false;
613 return strcmp(path.Path(), "/") == 0;
616 enum {
617 kNotConfirmed,
618 kConfirmedHomeMove,
619 kConfirmedAll
623 bool
624 ConfirmChangeIfWellKnownDirectory(const BEntry* entry, DestructiveAction action,
625 bool dontAsk, int32* confirmedAlready)
627 // Don't let the user casually move/change important files/folders
629 // This is a cheap replacement for having a real UID support turned
630 // on and not running as root all the time
632 if (confirmedAlready && *confirmedAlready == kConfirmedAll)
633 return true;
635 if (FSIsDeskDir(entry) || FSIsTrashDir(entry) || FSIsRootDir(entry))
636 return false;
638 if ((!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)
639 && !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY))
640 || DirectoryMatchesOrContains(entry, B_SYSTEM_TEMP_DIRECTORY))
641 // quick way out
642 return true;
644 BString warning;
645 bool requireOverride = true;
647 if (DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)) {
648 if (action == kRename) {
649 warning.SetTo(
650 B_TRANSLATE("If you rename the system folder or its "
651 "contents, you won't be able to boot %osName!\n\nAre you sure "
652 "you want to do this?\n\nTo rename the system folder or its "
653 "contents anyway, hold down the Shift key and click "
654 "\"Rename\"."));
655 } else if(action == kMove) {
656 warning.SetTo(
657 B_TRANSLATE("If you move the system folder or its "
658 "contents, you won't be able to boot %osName!\n\nAre you sure "
659 "you want to do this?\n\nTo move the system folder or its "
660 "contents anyway, hold down the Shift key and click "
661 "\"Move\"."));
662 } else {
663 warning.SetTo(
664 B_TRANSLATE("If you alter the system folder or its "
665 "contents, you won't be able to boot %osName!\n\nAre you sure "
666 "you want to do this?\n\nTo alter the system folder or its "
667 "contents anyway, hold down the Shift key and click "
668 "\"I know what I'm doing\"."));
670 } else if (DirectoryMatches(entry, B_USER_DIRECTORY)) {
671 if (action == kRename) {
672 warning .SetTo(
673 B_TRANSLATE("If you rename the home folder, %osName "
674 "may not behave properly!\n\nAre you sure you want to do this?"
675 "\n\nTo rename the home folder anyway, hold down the "
676 "Shift key and click \"Rename\"."));
677 } else if (action == kMove) {
678 warning .SetTo(
679 B_TRANSLATE("If you move the home folder, %osName "
680 "may not behave properly!\n\nAre you sure you want to do this?"
681 "\n\nTo move the home folder anyway, hold down the "
682 "Shift key and click \"Move\"."));
683 } else {
684 warning .SetTo(
685 B_TRANSLATE("If you alter the home folder, %osName "
686 "may not behave properly!\n\nAre you sure you want to do this?"
687 "\n\nTo alter the home folder anyway, hold down the "
688 "Shift key and click \"I know what I'm doing\"."));
690 } else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY)
691 || DirectoryMatchesOrContains(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
692 if (action == kRename) {
693 warning.SetTo(
694 B_TRANSLATE("If you rename %target, %osName may not behave "
695 "properly!\n\nAre you sure you want to do this?"));
696 } else if (action == kMove) {
697 warning.SetTo(
698 B_TRANSLATE("If you move %target, %osName may not behave "
699 "properly!\n\nAre you sure you want to do this?"));
700 } else {
701 warning.SetTo(
702 B_TRANSLATE("If you alter %target, %osName may not behave "
703 "properly!\n\nAre you sure you want to do this?"));
706 if (DirectoryMatchesOrContains(entry, "beos_mime",
707 B_USER_SETTINGS_DIRECTORY)
708 || DirectoryMatchesOrContains(entry, "beos_mime",
709 B_SYSTEM_SETTINGS_DIRECTORY)) {
710 warning.ReplaceFirst("%target", B_TRANSLATE("the MIME settings"));
711 requireOverride = false;
712 } else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) {
713 warning.ReplaceFirst("%target", B_TRANSLATE("the config folder"));
714 requireOverride = false;
715 } else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY)
716 || DirectoryMatches(entry, B_SYSTEM_SETTINGS_DIRECTORY)) {
717 warning.ReplaceFirst("%target", B_TRANSLATE("the settings folder"));
718 requireOverride = false;
719 } else {
720 // It was not a special directory/file after all. Allow renaming.
721 return true;
723 } else
724 return true;
726 if (dontAsk)
727 return false;
729 if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove
730 && !requireOverride)
731 // we already warned about moving home this time around
732 return true;
734 struct utsname name;
735 if (uname(&name) == -1)
736 warning.ReplaceFirst("%osName", "Haiku");
737 else
738 warning.ReplaceFirst("%osName", name.sysname);
740 BString buttonLabel;
741 if (action == kRename) {
742 buttonLabel = B_TRANSLATE_COMMENT("Rename", "button label");
743 } else if (action == kMove) {
744 buttonLabel = B_TRANSLATE_COMMENT("Move", "button label");
745 } else {
746 buttonLabel = B_TRANSLATE_COMMENT("I know what I'm doing",
747 "button label");
750 OverrideAlert* alert = new OverrideAlert("", warning.String(),
751 buttonLabel.String(), (requireOverride ? B_SHIFT_KEY : 0),
752 B_TRANSLATE("Cancel"), 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
753 alert->SetShortcut(1, B_ESCAPE);
754 if (alert->Go() == 1) {
755 if (confirmedAlready)
756 *confirmedAlready = kNotConfirmed;
757 return false;
760 if (confirmedAlready) {
761 if (!requireOverride)
762 *confirmedAlready = kConfirmedHomeMove;
763 else
764 *confirmedAlready = kConfirmedAll;
767 return true;
771 static status_t
772 InitCopy(CopyLoopControl* loopControl, uint32 moveMode,
773 BObjectList<entry_ref>* srcList, BVolume* dstVol, BDirectory* destDir,
774 entry_ref* destRef, bool preflightNameCheck, bool needSizeCalculation,
775 int32* collisionCount, ConflictCheckResult* preflightResult)
777 if (dstVol->IsReadOnly()) {
778 BAlert* alert = new BAlert("",
779 B_TRANSLATE("You can't move or copy items to read-only volumes."),
780 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
781 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
782 alert->Go();
783 return B_ERROR;
786 int32 numItems = srcList->CountItems();
787 int32 askOnceOnly = kNotConfirmed;
788 for (int32 index = 0; index < numItems; index++) {
789 // we could check for this while iterating through items in each of
790 // the copy loops, except it takes forever to call CalcItemsAndSize
791 BEntry entry((entry_ref*)srcList->ItemAt(index));
792 if (IsDisksWindowIcon(&entry)) {
793 BString errorStr;
794 if (moveMode == kCreateLink) {
795 errorStr.SetTo(
796 B_TRANSLATE("You cannot create a link to the root "
797 "directory."));
798 } else {
799 errorStr.SetTo(
800 B_TRANSLATE("You cannot copy or move the root "
801 "directory."));
804 BAlert* alert = new BAlert("", errorStr.String(),
805 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
806 B_WARNING_ALERT);
807 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
808 alert->Go();
809 return B_ERROR;
811 if (moveMode == kMoveSelectionTo
812 && !ConfirmChangeIfWellKnownDirectory(&entry, kMove,
813 false, &askOnceOnly)) {
814 return B_ERROR;
818 if (preflightNameCheck) {
819 ASSERT(collisionCount);
820 ASSERT(preflightResult);
822 *preflightResult = kPrompt;
823 *collisionCount = 0;
825 *preflightResult = PreFlightNameCheck(srcList, destDir,
826 collisionCount, moveMode);
827 if (*preflightResult == kCanceled) {
828 // user canceled
829 return B_ERROR;
833 // set up the status display
834 switch (moveMode) {
835 case kCopySelectionTo:
836 case kDuplicateSelection:
837 case kMoveSelectionTo:
839 loopControl->Init(moveMode == kMoveSelectionTo ? kMoveState
840 : kCopyState);
842 int32 totalItems = 0;
843 off_t totalSize = 0;
844 if (needSizeCalculation) {
845 if (CalcItemsAndSize(loopControl, srcList,
846 dstVol->BlockSize(), &totalItems, &totalSize)
847 != B_OK) {
848 return B_ERROR;
851 // check for free space before starting copy
852 if ((totalSize + (4* kKBSize)) >= dstVol->FreeBytes()) {
853 BAlert* alert = new BAlert("",
854 B_TRANSLATE_NOCOLLECT(kNoFreeSpace),
855 B_TRANSLATE("Cancel"),
856 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
857 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
858 alert->Go();
859 return B_ERROR;
863 loopControl->Init(totalItems, totalSize, destRef);
864 break;
867 case kCreateLink:
868 if (numItems > 10) {
869 // this will be fast, only put up status if lots of items
870 // moved, links created
871 loopControl->Init(kCreateLinkState);
872 loopControl->Init(numItems, numItems, destRef);
874 break;
877 return B_OK;
881 // ToDo:
882 // get rid of this cruft
883 bool
884 delete_ref(void* ref)
886 delete (entry_ref*)ref;
887 return false;
891 bool
892 delete_point(void* point)
894 delete (BPoint*)point;
895 return false;
899 static status_t
900 MoveTask(BObjectList<entry_ref>* srcList, BEntry* destEntry, BList* pointList,
901 uint32 moveMode)
903 ASSERT(!srcList->IsEmpty());
905 // extract information from src, dest models
906 // ## note that we're assuming all items come from the same volume
907 // ## by looking only at FirstItem here which is not a good idea
908 dev_t srcVolumeDevice = srcList->FirstItem()->device;
909 dev_t destVolumeDevice = srcVolumeDevice;
911 StatStruct deststat;
912 BVolume volume(srcVolumeDevice);
913 entry_ref destRef;
915 bool destIsTrash = false;
916 BDirectory destDir;
917 BDirectory* destDirToCheck = NULL;
918 bool needPreflightNameCheck = false;
919 bool sourceIsReadOnly = volume.IsReadOnly();
920 volume.Unset();
922 bool fromUndo = FSIsUndoMoveMode(moveMode);
923 moveMode = FSMoveMode(moveMode);
925 // if we're not passed a destEntry then we are supposed to move to trash
926 if (destEntry != NULL) {
927 destEntry->GetRef(&destRef);
929 destDir.SetTo(destEntry);
930 destDir.GetStat(&deststat);
931 destDirToCheck = &destDir;
933 destVolumeDevice = deststat.st_dev;
934 destIsTrash = FSIsTrashDir(destEntry);
935 volume.SetTo(destVolumeDevice);
937 needPreflightNameCheck = true;
938 } else if (moveMode == kDuplicateSelection) {
939 BEntry entry;
940 entry.SetTo(srcList->FirstItem());
941 entry.GetParent(&destDir);
942 volume.SetTo(srcVolumeDevice);
943 } else {
944 // move is to trash
945 destIsTrash = true;
947 FSGetTrashDir(&destDir, srcVolumeDevice);
948 volume.SetTo(srcVolumeDevice);
950 BEntry entry;
951 destDir.GetEntry(&entry);
952 destDirToCheck = &destDir;
954 entry.GetRef(&destRef);
957 // change the move mode if needed
958 if (moveMode == kCopySelectionTo && destIsTrash) {
959 // cannot copy to trash
960 moveMode = kMoveSelectionTo;
963 if (moveMode == kMoveSelectionTo && sourceIsReadOnly)
964 moveMode = kCopySelectionTo;
966 bool needSizeCalculation = true;
967 if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice)
968 || destIsTrash) {
969 needSizeCalculation = false;
972 // we need the undo object later on, so we create it no matter
973 // if we really need it or not (it's very lightweight)
974 MoveCopyUndo undo(srcList, destDir, pointList, moveMode);
975 if (fromUndo)
976 undo.Remove();
978 TrackerCopyLoopControl loopControl;
980 ConflictCheckResult conflictCheckResult = kPrompt;
981 int32 collisionCount = 0;
982 // TODO: Status item is created in InitCopy(), but it would be kind of
983 // neat to move all that into TrackerCopyLoopControl
984 status_t result = InitCopy(&loopControl, moveMode, srcList,
985 &volume, destDirToCheck, &destRef, needPreflightNameCheck,
986 needSizeCalculation, &collisionCount, &conflictCheckResult);
988 loopControl.SetSourceList(srcList);
990 if (result == B_OK) {
991 for (int32 i = 0; i < srcList->CountItems(); i++) {
992 BPoint* loc = (BPoint*)-1;
993 // a loc of -1 forces autoplacement, rather than copying the
994 // position of the original node
995 // TODO:
996 // Clean this mess up!
997 // What could be a cleaner design is to pass along some kind
998 // "filter" object that post-processes poses, i.e. adds the
999 // location or other stuff. It should not be a job of the
1000 // copy-engine.
1002 entry_ref* srcRef = srcList->ItemAt(i);
1004 if (moveMode == kDuplicateSelection) {
1005 BEntry entry(srcRef);
1006 entry.GetParent(&destDir);
1007 destDir.GetStat(&deststat);
1008 volume.SetTo(srcRef->device);
1011 // handle case where item is dropped into folder it already lives
1012 // in which could happen if dragging from a query window
1013 if (moveMode != kCreateLink
1014 && moveMode != kCreateRelativeLink
1015 && moveMode != kDuplicateSelection
1016 && !destIsTrash
1017 && (srcRef->device == destRef.device
1018 && srcRef->directory == deststat.st_ino)) {
1019 continue;
1022 if (loopControl.CheckUserCanceled())
1023 break;
1025 BEntry sourceEntry(srcRef);
1026 if (sourceEntry.InitCheck() != B_OK) {
1027 BString error(B_TRANSLATE("Error moving \"%name\"."));
1028 error.ReplaceFirst("%name", srcRef->name);
1029 BAlert* alert = new BAlert("", error.String(),
1030 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1031 B_WARNING_ALERT);
1032 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1033 alert->Go();
1034 break;
1037 // are we moving item to trash?
1038 if (destIsTrash) {
1039 if (pointList != NULL)
1040 loc = (BPoint*)pointList->ItemAt(i);
1042 result = MoveEntryToTrash(&sourceEntry, loc, undo);
1043 if (result != B_OK) {
1044 BString error(B_TRANSLATE("Error moving \"%name\" to Trash. "
1045 "(%error)"));
1046 error.ReplaceFirst("%name", srcRef->name);
1047 error.ReplaceFirst("%error", strerror(result));
1048 BAlert* alert = new BAlert("", error.String(),
1049 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1050 B_WARNING_ALERT);
1051 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1052 alert->Go();
1053 break;
1055 continue;
1058 // resolve name collisions and hierarchy problems
1059 if (CheckName(moveMode, &sourceEntry, &destDir,
1060 collisionCount > 1, conflictCheckResult) != B_OK) {
1061 // we will skip the current item, because we got a conflict
1062 // and were asked to or because there was some conflict
1064 // update the status because item got skipped and the status
1065 // will not get updated by the move call
1066 loopControl.UpdateStatus(srcRef->name, *srcRef, 1);
1068 continue;
1071 // get location to place this item
1072 if (pointList && moveMode != kCopySelectionTo) {
1073 loc = (BPoint*)pointList->ItemAt(i);
1075 BNode* src_node = GetWritableNode(&sourceEntry);
1076 if (src_node && src_node->InitCheck() == B_OK) {
1077 PoseInfo poseInfo;
1078 poseInfo.fInvisible = false;
1079 poseInfo.fInitedDirectory = deststat.st_ino;
1080 poseInfo.fLocation = *loc;
1081 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
1082 &poseInfo, sizeof(poseInfo));
1084 delete src_node;
1087 if (pointList)
1088 loc = (BPoint*)pointList->ItemAt(i);
1090 result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL,
1091 undo, &loopControl);
1092 if (result != B_OK)
1093 break;
1097 // duplicates of srcList, destFolder were created - dispose them
1098 delete srcList;
1099 delete destEntry;
1101 // delete file location list and all Points within
1102 if (pointList != NULL) {
1103 pointList->DoForEach(delete_point);
1104 delete pointList;
1107 return B_OK;
1111 class FailWithAlert {
1112 public:
1113 static void FailOnError(status_t error, const char* string,
1114 const char* name = NULL)
1116 if (error != B_OK)
1117 throw FailWithAlert(error, string, name);
1120 FailWithAlert(status_t error, const char* string, const char* name)
1122 fString(string),
1123 fName(name),
1124 fError(error)
1128 const char* fString;
1129 const char* fName;
1130 status_t fError;
1134 class MoveError {
1135 public:
1136 static void FailOnError(status_t error)
1138 if (error != B_OK)
1139 throw MoveError(error);
1142 MoveError(status_t error)
1144 fError(error)
1148 status_t fError;
1152 void
1153 CopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
1154 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1155 Undo &undo)
1157 if (loopControl->SkipEntry(srcFile, true))
1158 return;
1160 node_ref node;
1161 destDir->GetNodeRef(&node);
1162 BVolume volume(node.device);
1164 // check for free space first
1165 if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) {
1166 loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "",
1167 B_DEVICE_FULL, false);
1168 throw (status_t)B_DEVICE_FULL;
1171 char destName[B_FILE_NAME_LENGTH];
1172 srcFile->GetName(destName);
1173 entry_ref ref;
1174 srcFile->GetRef(&ref);
1176 loopControl->UpdateStatus(destName, ref, 1024, true);
1178 if (makeOriginalName) {
1179 BString suffix(" ");
1180 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1181 FSMakeOriginalName(destName, destDir, suffix.String());
1182 undo.UpdateEntry(srcFile, destName);
1185 BEntry conflictingEntry;
1186 if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) {
1187 switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir,
1188 false, false)) {
1189 case TrackerCopyLoopControl::kSkip:
1190 // we are about to ignore this entire directory
1191 return;
1193 case TrackerCopyLoopControl::kReplace:
1194 if (!conflictingEntry.IsDirectory()) {
1195 ThrowOnError(conflictingEntry.Remove());
1196 break;
1198 // fall through if not a directory
1199 case TrackerCopyLoopControl::kMerge:
1200 // This flag implies that the attributes should be kept
1201 // on the file. Just ignore it.
1202 break;
1206 try {
1207 LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc);
1208 } catch (status_t err) {
1209 if (err == kCopyCanceled)
1210 throw (status_t)err;
1212 if (err != B_OK) {
1213 if (!loopControl->FileError(
1214 B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err,
1215 true)) {
1216 throw (status_t)err;
1217 } else {
1218 // user selected continue in spite of error, update status bar
1219 loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size);
1226 #ifdef _SILENTLY_CORRECT_FILE_NAMES
1227 static bool
1228 CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName)
1230 // Is it a FAT32 file system?
1231 // (this is the only one we currently know about)
1233 BEntry target;
1234 destDir->GetEntry(&target);
1235 entry_ref targetRef;
1236 fs_info info;
1237 if (target.GetRef(&targetRef) == B_OK
1238 && fs_stat_dev(targetRef.device, &info) == B_OK
1239 && !strcmp(info.fsh_name, "fat")) {
1240 bool wasInvalid = false;
1242 // it's a FAT32 file system, now check the name
1244 int32 length = strlen(destName) - 1;
1245 while (destName[length] == '.') {
1246 // invalid name, just cut off the dot at the end
1247 destName[length--] = '\0';
1248 wasInvalid = true;
1251 char* invalid = destName;
1252 while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) {
1253 invalid[0] = '_';
1254 wasInvalid = true;
1257 return wasInvalid;
1260 return false;
1262 #endif
1265 static void
1266 LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir,
1267 char* destName, CopyLoopControl* loopControl, BPoint* loc)
1269 entry_ref ref;
1270 ThrowOnError(srcEntry->GetRef(&ref));
1272 if (S_ISLNK(srcStat->st_mode)) {
1273 // handle symbolic links
1274 BSymLink srcLink;
1275 BSymLink newLink;
1276 char linkpath[MAXPATHLEN];
1278 ThrowOnError(srcLink.SetTo(srcEntry));
1279 ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1);
1280 if (size < 0)
1281 ThrowOnError(size);
1282 ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink));
1284 node_ref destNodeRef;
1285 destDir->GetNodeRef(&destNodeRef);
1286 // copy or write new pose location as a first thing
1287 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcLink,
1288 &newLink, loc);
1290 BNodeInfo nodeInfo(&newLink);
1291 nodeInfo.SetType(B_LINK_MIMETYPE);
1293 newLink.SetPermissions(srcStat->st_mode);
1294 newLink.SetOwner(srcStat->st_uid);
1295 newLink.SetGroup(srcStat->st_gid);
1296 newLink.SetModificationTime(srcStat->st_mtime);
1297 newLink.SetCreationTime(srcStat->st_crtime);
1299 return;
1302 BFile srcFile(srcEntry, O_RDONLY);
1303 ThrowOnInitCheckError(&srcFile);
1305 const size_t kMinBufferSize = 1024* 128;
1306 const size_t kMaxBufferSize = 1024* 1024;
1308 size_t bufsize = kMinBufferSize;
1309 if ((off_t)bufsize < srcStat->st_size) {
1310 // File bigger than the buffer size: determine an optimal buffer size
1311 system_info sinfo;
1312 get_system_info(&sinfo);
1313 size_t freesize = static_cast<size_t>(
1314 (sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE);
1315 bufsize = freesize / 4;
1316 // take 1/4 of RAM max
1317 bufsize -= bufsize % (16* 1024);
1318 // Round to 16 KB boundaries
1319 if (bufsize < kMinBufferSize) {
1320 // at least kMinBufferSize
1321 bufsize = kMinBufferSize;
1322 } else if (bufsize > kMaxBufferSize) {
1323 // no more than kMaxBufferSize
1324 bufsize = kMaxBufferSize;
1328 BFile destFile(destDir, destName, O_RDWR | O_CREAT);
1329 #ifdef _SILENTLY_CORRECT_FILE_NAMES
1330 if ((destFile.InitCheck() == B_BAD_VALUE
1331 || destFile.InitCheck() == B_NOT_ALLOWED)
1332 && CreateFileSystemCompatibleName(destDir, destName)) {
1333 destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE);
1335 #endif
1337 ThrowOnInitCheckError(&destFile);
1339 node_ref destNodeRef;
1340 destDir->GetNodeRef(&destNodeRef);
1341 // copy or write new pose location as a first thing
1342 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcFile,
1343 &destFile, loc);
1345 char* buffer = new char[bufsize];
1346 try {
1347 // copy data portion of file
1348 while (true) {
1349 if (loopControl->CheckUserCanceled()) {
1350 // if copy was canceled, remove partial destination file
1351 destFile.Unset();
1353 BEntry destEntry;
1354 if (destDir->FindEntry(destName, &destEntry) == B_OK)
1355 destEntry.Remove();
1357 throw (status_t)kCopyCanceled;
1360 ASSERT(buffer);
1361 ssize_t bytes = srcFile.Read(buffer, bufsize);
1363 if (bytes > 0) {
1364 ssize_t updateBytes = 0;
1365 if (bytes > 32* 1024) {
1366 // when copying large chunks, update after read and after
1367 // write to get better update granularity
1368 updateBytes = bytes / 2;
1369 loopControl->UpdateStatus(NULL, ref, updateBytes, true);
1372 loopControl->ChecksumChunk(buffer, (size_t)bytes);
1374 ssize_t result = destFile.Write(buffer, (size_t)bytes);
1375 if (result != bytes)
1376 throw (status_t)B_ERROR;
1378 loopControl->UpdateStatus(NULL, ref, bytes - updateBytes,
1379 true);
1380 } else if (bytes < 0) {
1381 // read error
1382 throw (status_t)bytes;
1383 } else {
1384 // we are done
1385 break;
1389 CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize);
1390 } catch (...) {
1391 delete[] buffer;
1392 throw;
1395 destFile.SetPermissions(srcStat->st_mode);
1396 destFile.SetOwner(srcStat->st_uid);
1397 destFile.SetGroup(srcStat->st_gid);
1398 destFile.SetModificationTime(srcStat->st_mtime);
1399 destFile.SetCreationTime(srcStat->st_crtime);
1401 delete[] buffer;
1403 if (!loopControl->ChecksumFile(&ref)) {
1404 // File no good. Remove and quit.
1405 destFile.Unset();
1407 BEntry destEntry;
1408 if (destDir->FindEntry(destName, &destEntry) == B_OK)
1409 destEntry.Remove();
1410 throw (status_t)kUserCanceled;
1415 void
1416 CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode,
1417 void* buffer, size_t bufsize)
1419 // ToDo:
1420 // Add error checking
1421 // prior to coyping attributes, make sure indices are installed
1423 // When calling CopyAttributes on files, have to make sure destNode
1424 // is a BFile opened R/W
1426 srcNode->RewindAttrs();
1427 char name[256];
1428 while (srcNode->GetNextAttrName(name) == B_OK) {
1429 // Check to see if this attribute should be skipped.
1430 if (control->SkipAttribute(name))
1431 continue;
1433 attr_info info;
1434 if (srcNode->GetAttrInfo(name, &info) != B_OK)
1435 continue;
1437 // Check to see if this attribute should be overwritten when it
1438 // already exists.
1439 if (control->PreserveAttribute(name)) {
1440 attr_info dest_info;
1441 if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1442 continue;
1445 // Special case for a size 0 attribute. It wouldn't be written at all
1446 // otherwise.
1447 if (info.size == 0)
1448 destNode->WriteAttr(name, info.type, 0, buffer, 0);
1450 ssize_t bytes;
1451 ssize_t numToRead = (ssize_t)info.size;
1452 for (off_t offset = 0; numToRead > 0; offset += bytes) {
1453 size_t chunkSize = (size_t)numToRead;
1454 if (chunkSize > bufsize)
1455 chunkSize = bufsize;
1457 bytes = srcNode->ReadAttr(name, info.type, offset,
1458 buffer, chunkSize);
1460 if (bytes <= 0)
1461 break;
1463 destNode->WriteAttr(name, info.type, offset, buffer,
1464 (size_t)bytes);
1466 numToRead -= bytes;
1472 static void
1473 CopyFolder(BEntry* srcEntry, BDirectory* destDir,
1474 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName,
1475 Undo &undo, bool removeSource = false)
1477 BDirectory newDir;
1478 BEntry entry;
1479 status_t err = B_OK;
1480 bool createDirectory = true;
1481 BEntry existingEntry;
1483 if (loopControl->SkipEntry(srcEntry, false))
1484 return;
1486 entry_ref ref;
1487 srcEntry->GetRef(&ref);
1489 char destName[B_FILE_NAME_LENGTH];
1490 strlcpy(destName, ref.name, sizeof(destName));
1492 loopControl->UpdateStatus(ref.name, ref, 1024, true);
1494 if (makeOriginalName) {
1495 BString suffix(" ");
1496 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
1497 FSMakeOriginalName(destName, destDir, suffix.String());
1498 undo.UpdateEntry(srcEntry, destName);
1501 if (destDir->FindEntry(destName, &existingEntry) == B_OK) {
1502 // some entry with a conflicting name is already present in destDir
1503 // decide what to do about it
1504 bool isDirectory = existingEntry.IsDirectory();
1506 switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir,
1507 true, isDirectory)) {
1508 case TrackerCopyLoopControl::kSkip:
1509 // we are about to ignore this entire directory
1510 return;
1513 case TrackerCopyLoopControl::kReplace:
1514 if (!isDirectory) {
1515 // conflicting with a file or symbolic link, remove entry
1516 ThrowOnError(existingEntry.Remove());
1517 break;
1519 // fall through if directory, do not replace.
1520 case TrackerCopyLoopControl::kMerge:
1521 ASSERT(isDirectory);
1522 // do not create a new directory, use the current one
1523 newDir.SetTo(&existingEntry);
1524 createDirectory = false;
1525 break;
1529 // loop through everything in src folder and copy it to new folder
1530 BDirectory srcDir(srcEntry);
1531 srcDir.Rewind();
1533 // create a new folder inside of destination folder
1534 if (createDirectory) {
1535 err = destDir->CreateDirectory(destName, &newDir);
1536 #ifdef _SILENTLY_CORRECT_FILE_NAMES
1537 if (err == B_BAD_VALUE) {
1538 // check if it's an invalid name on a FAT32 file system
1539 if (CreateFileSystemCompatibleName(destDir, destName))
1540 err = destDir->CreateDirectory(destName, &newDir);
1542 #endif
1543 if (err != B_OK) {
1544 if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT(
1545 kFolderErrorString), destName, err, true)) {
1546 throw err;
1549 // will allow rest of copy to continue
1550 return;
1554 char* buffer;
1555 if (createDirectory && err == B_OK
1556 && (buffer = (char*)malloc(32768)) != 0) {
1557 CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768);
1558 // don't copy original pose location if new location passed
1559 free(buffer);
1562 StatStruct statbuf;
1563 srcDir.GetStat(&statbuf);
1564 dev_t sourceDeviceID = statbuf.st_dev;
1566 // copy or write new pose location
1567 node_ref destNodeRef;
1568 destDir->GetNodeRef(&destNodeRef);
1569 SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir,
1570 &newDir, loc);
1572 while (srcDir.GetNextEntry(&entry) == B_OK) {
1574 if (loopControl->CheckUserCanceled())
1575 throw (status_t)kUserCanceled;
1577 entry.GetStat(&statbuf);
1579 if (S_ISDIR(statbuf.st_mode)) {
1581 // entry is a mount point, do not copy it
1582 if (statbuf.st_dev != sourceDeviceID) {
1583 PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n",
1584 statbuf.st_dev, sourceDeviceID));
1585 continue;
1588 CopyFolder(&entry, &newDir, loopControl, 0, false, undo,
1589 removeSource);
1590 if (removeSource)
1591 FSDeleteFolder(&entry, loopControl, true, true, false);
1592 } else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
1593 CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo);
1594 if (removeSource)
1595 entry.Remove();
1596 } else {
1597 // Ignore special files
1600 if (removeSource)
1601 srcEntry->Remove();
1602 else
1603 srcEntry->Unset();
1607 status_t
1608 RecursiveMove(BEntry* entry, BDirectory* destDir,
1609 CopyLoopControl* loopControl)
1611 const char* name = entry->Name();
1613 if (destDir->Contains(name)) {
1614 BPath path (destDir, name);
1615 BDirectory subDir (path.Path());
1616 entry_ref ref;
1617 entry->GetRef(&ref);
1618 BDirectory source(&ref);
1619 if (source.InitCheck() == B_OK) {
1620 source.Rewind();
1621 BEntry current;
1622 while (source.GetNextEntry(&current) == B_OK) {
1623 if (current.IsDirectory()) {
1624 RecursiveMove(&current, &subDir, loopControl);
1625 current.Remove();
1626 } else {
1627 name = current.Name();
1628 if (loopControl->OverwriteOnConflict(&current, name,
1629 &subDir, true, false)
1630 != TrackerCopyLoopControl::kSkip) {
1631 MoveError::FailOnError(current.MoveTo(&subDir,
1632 NULL, true));
1637 entry->Remove();
1638 } else
1639 MoveError::FailOnError(entry->MoveTo(destDir));
1641 return B_OK;
1644 status_t
1645 MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode,
1646 const char* newName, Undo &undo, CopyLoopControl* loopControl)
1648 entry_ref ref;
1649 try {
1650 node_ref destNode;
1651 StatStruct statbuf;
1652 MoveError::FailOnError(entry->GetStat(&statbuf));
1653 MoveError::FailOnError(entry->GetRef(&ref));
1654 MoveError::FailOnError(destDir->GetNodeRef(&destNode));
1656 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
1657 PoseInfo poseInfo;
1658 char name[B_FILE_NAME_LENGTH];
1659 strlcpy(name, ref.name, sizeof(name));
1661 BSymLink link;
1662 BString suffix(" ");
1663 suffix << B_TRANSLATE_COMMENT("link", "filename link"),
1664 FSMakeOriginalName(name, destDir, suffix.String());
1665 undo.UpdateEntry(entry, name);
1667 BPath path;
1668 entry->GetPath(&path);
1669 if (loc && loc != (BPoint*)-1) {
1670 poseInfo.fInvisible = false;
1671 poseInfo.fInitedDirectory = destNode.node;
1672 poseInfo.fLocation = *loc;
1675 status_t err = B_ERROR;
1677 if (moveMode == kCreateRelativeLink) {
1678 if (statbuf.st_dev == destNode.device) {
1679 // relative link only works on the same device
1680 char oldwd[B_PATH_NAME_LENGTH];
1681 getcwd(oldwd, B_PATH_NAME_LENGTH);
1683 BEntry destEntry;
1684 destDir -> GetEntry(&destEntry);
1685 BPath destPath;
1686 destEntry.GetPath(&destPath);
1688 chdir(destPath.Path());
1689 // change working dir to target dir
1691 BString destString(destPath.Path());
1692 destString.Append("/");
1694 BString srcString(path.Path());
1695 srcString.RemoveLast(path.Leaf());
1697 // find index while paths are the same
1699 const char* src = srcString.String();
1700 const char* dest = destString.String();
1701 const char* lastFolderSrc = src;
1702 const char* lastFolderDest = dest;
1704 while (*src && *dest && *src == *dest) {
1705 ++src;
1706 if (*dest++ == '/') {
1707 lastFolderSrc = src;
1708 lastFolderDest = dest;
1711 src = lastFolderSrc;
1712 dest = lastFolderDest;
1714 BString source;
1715 if (*dest == '\0' && *src != '\0') {
1716 // source is deeper in the same tree than the target
1717 source.Append(src);
1718 } else if (*dest != '\0') {
1719 // target is deeper in the same tree than the source
1720 while (*dest) {
1721 if (*dest == '/')
1722 source.Prepend("../");
1723 ++dest;
1725 source.Append(src);
1728 // else source and target are in the same dir
1730 source.Append(path.Leaf());
1731 err = destDir->CreateSymLink(name, source.String(),
1732 &link);
1734 chdir(oldwd);
1735 // change working dir back to original
1736 } else
1737 moveMode = kCreateLink;
1738 // fall back to absolute link mode
1741 if (moveMode == kCreateLink)
1742 err = destDir->CreateSymLink(name, path.Path(), &link);
1744 if (err == B_UNSUPPORTED) {
1745 throw FailWithAlert(err,
1746 B_TRANSLATE("The target disk does not support "
1747 "creating links."), NULL);
1750 FailWithAlert::FailOnError(err,
1751 B_TRANSLATE("Error creating link to \"%name\"."),
1752 ref.name);
1754 if (loc && loc != (BPoint*)-1) {
1755 link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
1756 sizeof(PoseInfo));
1759 BNodeInfo nodeInfo(&link);
1760 nodeInfo.SetType(B_LINK_MIMETYPE);
1761 return B_OK;
1764 // if move is on same volume don't copy
1765 if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo
1766 && moveMode != kDuplicateSelection) {
1768 // for "Move" the size for status is always 1 - since file
1769 // size is irrelevant when simply moving to a new folder
1770 loopControl->UpdateStatus(ref.name, ref, 1);
1771 if (entry->IsDirectory())
1772 return RecursiveMove(entry, destDir, loopControl);
1774 MoveError::FailOnError(entry->MoveTo(destDir, newName));
1775 } else {
1776 bool makeOriginalName = (moveMode == kDuplicateSelection);
1777 if (S_ISDIR(statbuf.st_mode)) {
1778 CopyFolder(entry, destDir, loopControl, loc, makeOriginalName,
1779 undo, moveMode == kMoveSelectionTo);
1780 } else {
1781 CopyFile(entry, &statbuf, destDir, loopControl, loc,
1782 makeOriginalName, undo);
1783 if (moveMode == kMoveSelectionTo)
1784 entry->Remove();
1787 } catch (status_t error) {
1788 // no alert, was already taken care of before
1789 return error;
1790 } catch (MoveError error) {
1791 BString errorString(B_TRANSLATE("Error moving \"%name\""));
1792 errorString.ReplaceFirst("%name", ref.name);
1793 BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"),
1794 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1795 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1796 alert->Go();
1797 return error.fError;
1798 } catch (FailWithAlert error) {
1799 BString buffer(error.fString);
1800 if (error.fName != NULL)
1801 buffer.ReplaceFirst("%name", error.fName);
1802 else
1803 buffer << error.fString;
1805 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"),
1806 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1807 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1808 alert->Go();
1810 return error.fError;
1813 return B_OK;
1817 void
1818 FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList)
1820 LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList,
1821 (BEntry*)NULL, pointList, kDuplicateSelection);
1825 #if 0
1826 status_t
1827 FSCopyFolder(BEntry* srcEntry, BDirectory* destDir,
1828 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
1831 CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName);
1832 catch (status_t error) {
1833 return error;
1835 return B_OK;
1837 #endif
1840 status_t
1841 FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes)
1843 char* buffer = new char[1024];
1845 // copy the attributes
1846 srcNode->RewindAttrs();
1847 char name[256];
1848 while (srcNode->GetNextAttrName(name) == B_OK) {
1849 attr_info info;
1850 if (srcNode->GetAttrInfo(name, &info) != B_OK)
1851 continue;
1853 attr_info dest_info;
1854 if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
1855 continue;
1857 ssize_t bytes;
1858 ssize_t numToRead = (ssize_t)info.size;
1859 for (off_t offset = 0; numToRead > 0; offset += bytes) {
1860 size_t chunkSize = (size_t)numToRead;
1861 if (chunkSize > 1024)
1862 chunkSize = 1024;
1864 bytes = srcNode->ReadAttr(name, info.type, offset, buffer,
1865 chunkSize);
1867 if (bytes <= 0)
1868 break;
1870 destNode->WriteAttr(name, info.type, offset, buffer,
1871 (size_t)bytes);
1873 numToRead -= bytes;
1876 delete[] buffer;
1878 // copy the file stats
1879 struct stat srcStat;
1880 srcNode->GetStat(&srcStat);
1881 destNode->SetPermissions(srcStat.st_mode);
1882 destNode->SetOwner(srcStat.st_uid);
1883 destNode->SetGroup(srcStat.st_gid);
1884 if (copyTimes) {
1885 destNode->SetModificationTime(srcStat.st_mtime);
1886 destNode->SetCreationTime(srcStat.st_crtime);
1889 return B_OK;
1893 #if 0
1894 status_t
1895 FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir,
1896 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName)
1898 try {
1899 CopyFile(srcFile, srcStat, destDir, loopControl, loc,
1900 makeOriginalName);
1901 } catch (status_t error) {
1902 return error;
1905 return B_OK;
1907 #endif
1910 static status_t
1911 MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo)
1913 BDirectory trash_dir;
1914 entry_ref ref;
1915 status_t result = entry->GetRef(&ref);
1916 if (result != B_OK)
1917 return result;
1919 node_ref nodeRef;
1920 result = entry->GetNodeRef(&nodeRef);
1921 if (result != B_OK)
1922 return result;
1924 StatStruct statbuf;
1925 result = entry->GetStat(&statbuf);
1926 if (entry->GetStat(&statbuf) != B_OK)
1927 return result;
1929 // if it's a directory close the window and any child dir windows
1930 if (S_ISDIR(statbuf.st_mode)) {
1931 BDirectory dir(entry);
1933 // if it's a volume, try to unmount
1934 if (dir.IsRootDirectory()) {
1935 BVolume volume(nodeRef.device);
1936 BVolume boot;
1938 BVolumeRoster().GetBootVolume(&boot);
1939 if (volume == boot) {
1940 char name[B_FILE_NAME_LENGTH];
1941 volume.GetName(name);
1942 BString buffer(
1943 B_TRANSLATE("Cannot unmount the boot volume \"%name\"."));
1944 buffer.ReplaceFirst("%name", name);
1945 BAlert* alert = new BAlert("", buffer.String(),
1946 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
1947 B_WARNING_ALERT);
1948 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1949 alert->Go();
1950 } else {
1951 BMessage message(kUnmountVolume);
1952 message.AddInt32("device_id", volume.Device());
1953 be_app->PostMessage(&message);
1955 return B_OK;
1958 // get trash directory on same volume as item being moved
1959 result = FSGetTrashDir(&trash_dir, nodeRef.device);
1960 if (result != B_OK)
1961 return result;
1963 // check hierarchy before moving
1964 BEntry trashEntry;
1965 trash_dir.GetEntry(&trashEntry);
1967 if (dir == trash_dir || dir.Contains(&trashEntry)) {
1968 BAlert* alert = new BAlert("",
1969 B_TRANSLATE("You cannot put the selected item(s) "
1970 "into the trash."),
1971 B_TRANSLATE("OK"),
1972 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1973 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1974 alert->Go();
1976 // return no error so we don't get two dialogs
1977 return B_OK;
1980 BMessage message(kCloseWindowAndChildren);
1982 node_ref parentNode;
1983 parentNode.device = statbuf.st_dev;
1984 parentNode.node = statbuf.st_ino;
1985 message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref));
1986 be_app->PostMessage(&message);
1987 } else {
1988 // get trash directory on same volume as item being moved
1989 result = FSGetTrashDir(&trash_dir, nodeRef.device);
1990 if (result != B_OK)
1991 return result;
1994 // make sure name doesn't conflict with anything in trash already
1995 char name[B_FILE_NAME_LENGTH];
1996 strlcpy(name, ref.name, sizeof(name));
1997 if (trash_dir.Contains(name)) {
1998 BString suffix(" ");
1999 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"),
2000 FSMakeOriginalName(name, &trash_dir, suffix.String());
2001 undo.UpdateEntry(entry, name);
2004 BNode* src_node = 0;
2005 if (loc && loc != (BPoint*)-1
2006 && (src_node = GetWritableNode(entry, &statbuf)) != 0) {
2007 trash_dir.GetStat(&statbuf);
2008 PoseInfo poseInfo;
2009 poseInfo.fInvisible = false;
2010 poseInfo.fInitedDirectory = statbuf.st_ino;
2011 poseInfo.fLocation = *loc;
2012 src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2013 sizeof(poseInfo));
2014 delete src_node;
2017 BNode node(entry);
2018 BPath path;
2019 // Get path of entry before it's moved to the trash
2020 // and write it to the file as an attribute
2021 if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) {
2022 BString originalPath(path.Path());
2023 node.WriteAttrString(kAttrOriginalPath, &originalPath);
2026 TrackerCopyLoopControl loopControl;
2027 MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo,
2028 &loopControl);
2029 return B_OK;
2033 ConflictCheckResult
2034 PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir,
2035 int32* collisionCount, uint32 moveMode)
2037 // count the number of name collisions in dest folder
2038 *collisionCount = 0;
2040 int32 count = srcList->CountItems();
2041 for (int32 i = 0; i < count; i++) {
2042 entry_ref* srcRef = srcList->ItemAt(i);
2043 BEntry entry(srcRef);
2044 BDirectory parent;
2045 entry.GetParent(&parent);
2047 if (parent != *destDir && destDir->Contains(srcRef->name))
2048 (*collisionCount)++;
2051 // prompt user only if there is more than one collision, otherwise the
2052 // single collision case will be handled as a "Prompt" case by CheckName
2053 if (*collisionCount > 1) {
2054 const char* verb = (moveMode == kMoveSelectionTo)
2055 ? B_TRANSLATE("moving") : B_TRANSLATE("copying");
2056 BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr));
2057 replaceMsg.ReplaceAll("%verb", verb);
2059 BAlert* alert = new BAlert();
2060 alert->SetText(replaceMsg.String());
2061 alert->AddButton(B_TRANSLATE("Cancel"));
2062 alert->AddButton(B_TRANSLATE("Prompt"));
2063 alert->AddButton(B_TRANSLATE("Skip all"));
2064 alert->AddButton(B_TRANSLATE("Replace all"));
2065 alert->SetShortcut(0, B_ESCAPE);
2066 switch (alert->Go()) {
2067 case 0:
2068 return kCanceled;
2070 case 1:
2071 // user selected "Prompt"
2072 return kPrompt;
2074 case 2:
2075 // user selected "Skip all"
2076 return kSkipAll;
2078 case 3:
2079 // user selected "Replace all"
2080 return kReplaceAll;
2084 return kNoConflicts;
2088 void
2089 FileStatToString(StatStruct* stat, char* buffer, int32 length)
2091 tm timeData;
2092 localtime_r(&stat->st_mtime, &timeData);
2094 BString size;
2095 static BMessageFormat format(
2096 B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}"));
2097 format.Format(size, stat->st_size);
2098 uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String());
2100 strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData);
2104 status_t
2105 CheckName(uint32 moveMode, const BEntry* sourceEntry,
2106 const BDirectory* destDir, bool multipleCollisions,
2107 ConflictCheckResult& conflictResolution)
2109 if (moveMode == kDuplicateSelection) {
2110 // when duplicating, we will never have a conflict
2111 return B_OK;
2114 // see if item already exists in destination dir
2115 const char* name = sourceEntry->Name();
2116 bool sourceIsDirectory = sourceEntry->IsDirectory();
2118 BDirectory srcDirectory;
2119 if (sourceIsDirectory) {
2120 srcDirectory.SetTo(sourceEntry);
2121 BEntry destEntry;
2122 destDir->GetEntry(&destEntry);
2124 if (moveMode != kCreateLink && moveMode != kCreateRelativeLink
2125 && (srcDirectory == *destDir
2126 || srcDirectory.Contains(&destEntry))) {
2127 BAlert* alert = new BAlert("",
2128 B_TRANSLATE("You can't move a folder into itself "
2129 "or any of its own sub-folders."), B_TRANSLATE("OK"),
2130 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2131 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2132 alert->Go();
2133 return B_ERROR;
2137 if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink
2138 && moveMode != kCreateRelativeLink) {
2139 BAlert* alert = new BAlert("",
2140 B_TRANSLATE("You can't move or copy the trash."),
2141 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2142 B_WARNING_ALERT);
2143 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2144 alert->Go();
2145 return B_ERROR;
2148 BEntry entry;
2149 if (destDir->FindEntry(name, &entry) != B_OK) {
2150 // no conflict, return
2151 return B_OK;
2154 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2155 // if we are creating link in the same directory, the conflict will
2156 // be handled later by giving the link a unique name
2157 sourceEntry->GetParent(&srcDirectory);
2159 if (srcDirectory == *destDir)
2160 return B_OK;
2163 bool destIsDir = entry.IsDirectory();
2164 // be sure not to replace the parent directory of the item being moved
2165 if (destIsDir) {
2166 BDirectory targetDir(&entry);
2167 if (targetDir.Contains(sourceEntry)) {
2168 BAlert* alert = new BAlert("",
2169 B_TRANSLATE("You can't replace a folder "
2170 "with one of its sub-folders."),
2171 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2172 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2173 alert->Go();
2174 return B_ERROR;
2178 // ensure that the user isn't trying to replace a file with folder
2179 // or vice-versa
2180 if (moveMode != kCreateLink
2181 && moveMode != kCreateRelativeLink
2182 && destIsDir != sourceIsDirectory) {
2183 BAlert* alert = new BAlert("", sourceIsDirectory
2184 ? B_TRANSLATE("You cannot replace a file with a folder or a "
2185 "symbolic link.")
2186 : B_TRANSLATE("You cannot replace a folder or a symbolic link "
2187 "with a file."), B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL,
2188 B_WARNING_ALERT);
2189 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2190 alert->Go();
2191 return B_ERROR;
2194 if (conflictResolution == kSkipAll)
2195 return B_ERROR;
2197 if (conflictResolution != kReplaceAll) {
2198 // prompt user to determine whether to replace or not
2199 BString replaceMsg;
2201 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
2202 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr));
2203 replaceMsg.ReplaceFirst("%name", name);
2204 } else if (sourceEntry->IsDirectory()) {
2205 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr));
2206 replaceMsg.ReplaceFirst("%name", name);
2207 replaceMsg.ReplaceFirst("%verb",
2208 moveMode == kMoveSelectionTo
2209 ? B_TRANSLATE("moving")
2210 : B_TRANSLATE("copying"));
2211 } else {
2212 char sourceBuffer[96], destBuffer[96];
2213 StatStruct statBuffer;
2215 if (!sourceEntry->IsDirectory()
2216 && sourceEntry->GetStat(&statBuffer) == B_OK) {
2217 FileStatToString(&statBuffer, sourceBuffer, 96);
2218 } else
2219 sourceBuffer[0] = '\0';
2221 if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
2222 FileStatToString(&statBuffer, destBuffer, 96);
2223 else
2224 destBuffer[0] = '\0';
2226 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr));
2227 replaceMsg.ReplaceAll("%name", name);
2228 replaceMsg.ReplaceFirst("%dest", destBuffer);
2229 replaceMsg.ReplaceFirst("%src", sourceBuffer);
2230 replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo
2231 ? B_TRANSLATE("moving") : B_TRANSLATE("copying"));
2234 // special case single collision (don't need Replace All shortcut)
2235 BAlert* alert;
2236 if (multipleCollisions || sourceIsDirectory) {
2237 alert = new BAlert();
2238 alert->SetText(replaceMsg.String());
2239 alert->AddButton(B_TRANSLATE("Skip"));
2240 alert->AddButton(B_TRANSLATE("Skip all"));
2241 alert->AddButton(B_TRANSLATE("Replace"));
2242 alert->AddButton(B_TRANSLATE("Replace all"));
2243 switch (alert->Go()) {
2244 case 0:
2245 conflictResolution = kCanceled;
2246 return B_ERROR;
2247 case 1:
2248 conflictResolution = kSkipAll;
2249 return B_ERROR;
2250 case 2:
2251 conflictResolution = kReplace;
2252 break;
2253 case 3:
2254 conflictResolution = kReplaceAll;
2255 break;
2257 } else {
2258 alert = new BAlert("", replaceMsg.String(),
2259 B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2260 alert->SetShortcut(0, B_ESCAPE);
2261 switch (alert->Go()) {
2262 case 0:
2263 conflictResolution = kCanceled;
2264 return B_ERROR;
2265 case 1:
2266 conflictResolution = kReplace;
2267 break;
2272 // delete destination item
2273 if (destIsDir)
2274 return B_OK;
2276 status_t status = entry.Remove();
2277 if (status != B_OK) {
2278 BString error(B_TRANSLATE("There was a problem trying to replace "
2279 "\"%name\". The item might be open or busy."));
2280 error.ReplaceFirst("%name", name);
2281 BAlert* alert = new BAlert("", error.String(),
2282 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2283 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2284 alert->Go();
2287 return status;
2291 status_t
2292 FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl,
2293 bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus)
2295 BDirectory dir(dirEntry);
2297 // loop through everything in folder and delete it, skipping trouble files
2298 BEntry entry;
2299 while (dir.GetNextEntry(&entry) == B_OK) {
2300 entry_ref ref;
2301 entry.GetRef(&ref);
2303 if (loopControl->CheckUserCanceled())
2304 return kTrashCanceled;
2306 status_t status;
2308 if (entry.IsDirectory())
2309 status = FSDeleteFolder(&entry, loopControl, updateStatus, true,
2310 upateFileNameInStatus);
2311 else {
2312 status = entry.Remove();
2313 if (updateStatus) {
2314 loopControl->UpdateStatus(upateFileNameInStatus ? ref.name
2315 : "", ref, 1, true);
2319 if (status == kTrashCanceled)
2320 return kTrashCanceled;
2322 if (status != B_OK) {
2323 loopControl->FileError(B_TRANSLATE_NOCOLLECT(
2324 kFileDeleteErrorString), ref.name, status, false);
2328 if (loopControl->CheckUserCanceled())
2329 return kTrashCanceled;
2331 entry_ref ref;
2332 dirEntry->GetRef(&ref);
2334 if (updateStatus && deleteTopDir)
2335 loopControl->UpdateStatus(NULL, ref, 1);
2337 if (deleteTopDir)
2338 return dirEntry->Remove();
2340 return B_OK;
2344 void
2345 FSMakeOriginalName(BString &string, const BDirectory* destDir,
2346 const char* suffix)
2348 if (!destDir->Contains(string.String()))
2349 return;
2351 FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
2352 const_cast<BDirectory*>(destDir), suffix ? suffix : " copy");
2353 string.UnlockBuffer();
2357 void
2358 FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix)
2360 char root[B_FILE_NAME_LENGTH];
2361 char copybase[B_FILE_NAME_LENGTH];
2362 char temp_name[B_FILE_NAME_LENGTH + 10];
2363 int32 fnum;
2365 // is this name already original?
2366 if (!destDir->Contains(name))
2367 return;
2369 // Determine if we're copying a 'copy'. This algorithm isn't perfect.
2370 // If you're copying a file whose REAL name ends with 'copy' then
2371 // this method will return "<filename> 1", not "<filename> copy"
2373 // However, it will correctly handle file that contain 'copy'
2374 // elsewhere in their name.
2376 bool copycopy = false; // are we copying a copy?
2377 int32 len = (int32)strlen(name);
2378 char* p = name + len - 1; // get pointer to end os name
2380 // eat up optional numbers (if were copying "<filename> copy 34")
2381 while ((p > name) && isdigit(*p))
2382 p--;
2384 // eat up optional spaces
2385 while ((p > name) && isspace(*p))
2386 p--;
2388 // now look for the phrase " copy"
2389 if (p > name) {
2390 // p points to the last char of the word. For example, 'y' in 'copy'
2392 if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
2393 // we found 'copy' in the right place.
2394 // so truncate after 'copy'
2395 *(p + 1) = '\0';
2396 copycopy = true;
2398 // save the 'root' name of the file, for possible later use.
2399 // that is copy everything but trailing " copy". Need to
2400 // NULL terminate after copy
2401 strncpy(root, name, (uint32)((p - name) - 4));
2402 root[(p - name) - 4] = '\0';
2406 if (!copycopy) {
2407 // The name can't be longer than B_FILE_NAME_LENGTH.
2408 // The algoritm adds " copy XX" to the name. That's 8 characters.
2409 // B_FILE_NAME_LENGTH already accounts for NULL termination so we
2410 // don't need to save an extra char at the end.
2411 if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
2412 // name is too long - truncate it!
2413 name[B_FILE_NAME_LENGTH - 8] = '\0';
2416 strcpy(root, name); // save root name
2417 strcat(name, suffix);
2420 strcpy(copybase, name);
2422 // if name already exists then add a number
2423 fnum = 1;
2424 strcpy(temp_name, name);
2425 while (destDir->Contains(temp_name)) {
2426 sprintf(temp_name, "%s %" B_PRId32, copybase, ++fnum);
2428 if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) {
2429 // The name has grown too long. Maybe we just went from
2430 // "<filename> copy 9" to "<filename> copy 10" and that extra
2431 // character was too much. The solution is to further
2432 // truncate the 'root' name and continue.
2433 // ??? should we reset fnum or not ???
2434 root[strlen(root) - 1] = '\0';
2435 sprintf(temp_name, "%s%s %" B_PRId32, root, suffix, fnum);
2439 ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1)));
2440 strcpy(name, temp_name);
2444 status_t
2445 FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl,
2446 BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount)
2448 dir->Rewind();
2449 BEntry entry;
2450 while (dir->GetNextEntry(&entry) == B_OK) {
2451 // be sure window hasn't closed
2452 if (window && window->StopCalc())
2453 return B_OK;
2455 if (loopControl->CheckUserCanceled())
2456 return kUserCanceled;
2458 StatStruct statbuf;
2459 status_t status = entry.GetStat(&statbuf);
2460 if (status != B_OK)
2461 return status;
2463 (*_runningSize) += statbuf.st_blocks* 512;
2465 if (S_ISDIR(statbuf.st_mode)) {
2466 BDirectory subdir(&entry);
2467 (*_dirCount)++;
2468 status = FSRecursiveCalcSize(window, loopControl, &subdir,
2469 _runningSize, _fileCount, _dirCount);
2470 if (status != B_OK)
2471 return status;
2472 } else
2473 (*_fileCount)++;
2475 return B_OK;
2479 status_t
2480 CalcItemsAndSize(CopyLoopControl* loopControl,
2481 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount,
2482 off_t* totalSize)
2484 int32 fileCount = 0;
2485 int32 dirCount = 0;
2487 // check block size for sanity
2488 if (blockSize < 0) {
2489 // This would point at an error to retrieve the block size from
2490 // the target volume. The code below cannot be used, it is only
2491 // meant to get the block size when item operations happen on
2492 // the source volume.
2493 blockSize = 2048;
2494 } else if (blockSize < 1024) {
2495 blockSize = 1024;
2496 if (entry_ref* ref = refList->ItemAt(0)) {
2497 // TODO: This assumes all entries in the list share the same
2498 // volume...
2499 BVolume volume(ref->device);
2500 if (volume.InitCheck() == B_OK)
2501 blockSize = volume.BlockSize();
2504 // File systems like ReiserFS may advertize a large block size, but
2505 // stuff is still packed into blocks, so clamp maximum block size.
2506 if (blockSize > 8192)
2507 blockSize = 8192;
2509 int32 num_items = refList->CountItems();
2510 for (int32 i = 0; i < num_items; i++) {
2511 entry_ref* ref = refList->ItemAt(i);
2512 BEntry entry(ref);
2513 StatStruct statbuf;
2514 entry.GetStat(&statbuf);
2516 if (loopControl->CheckUserCanceled())
2517 return kUserCanceled;
2519 if (S_ISDIR(statbuf.st_mode)) {
2520 BDirectory dir(&entry);
2521 dirCount++;
2522 (*totalSize) += blockSize;
2523 status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir,
2524 totalSize, &fileCount, &dirCount);
2525 if (result != B_OK)
2526 return result;
2527 } else {
2528 fileCount++;
2529 (*totalSize) += statbuf.st_size + blockSize;
2533 *totalCount += (fileCount + dirCount);
2534 return B_OK;
2538 status_t
2539 FSGetTrashDir(BDirectory* trashDir, dev_t dev)
2541 if (trashDir == NULL)
2542 return B_BAD_VALUE;
2544 BVolume volume(dev);
2545 status_t result = volume.InitCheck();
2546 if (result != B_OK)
2547 return result;
2549 BPath path;
2550 result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume);
2551 if (result != B_OK)
2552 return result;
2554 result = trashDir->SetTo(path.Path());
2555 if (result != B_OK) {
2556 // Trash directory does not exist yet, create it.
2557 result = create_directory(path.Path(), 0755);
2558 if (result != B_OK)
2559 return result;
2561 result = trashDir->SetTo(path.Path());
2562 if (result != B_OK)
2563 return result;
2565 // make Trash directory invisible
2566 StatStruct sbuf;
2567 trashDir->GetStat(&sbuf);
2569 PoseInfo poseInfo;
2570 poseInfo.fInvisible = true;
2571 poseInfo.fInitedDirectory = sbuf.st_ino;
2572 trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
2573 sizeof(PoseInfo));
2576 // set Trash icons (if they haven't already been set)
2577 attr_info attrInfo;
2578 size_t size;
2579 const void* data;
2580 if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2581 data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
2582 if (data != NULL)
2583 trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2586 if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2587 data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
2588 if (data != NULL)
2589 trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2592 if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2593 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2594 R_TrashIcon, &size);
2595 if (data != NULL)
2596 trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2599 return B_OK;
2603 status_t
2604 FSGetDeskDir(BDirectory* deskDir)
2606 if (deskDir == NULL)
2607 return B_BAD_VALUE;
2609 BPath path;
2610 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2611 if (result != B_OK)
2612 return result;
2614 result = deskDir->SetTo(path.Path());
2615 if (result != B_OK)
2616 return result;
2618 // set Desktop icons (if they haven't already been set)
2619 attr_info attrInfo;
2620 size_t size;
2621 const void* data;
2622 if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2623 data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size);
2624 if (data != NULL)
2625 deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
2628 if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2629 data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size);
2630 if (data != NULL)
2631 deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
2634 if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
2635 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
2636 R_DeskIcon, &size);
2637 if (data != NULL)
2638 deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
2641 return B_OK;
2645 status_t
2646 FSGetBootDeskDir(BDirectory* deskDir)
2648 BVolume bootVolume;
2649 BVolumeRoster().GetBootVolume(&bootVolume);
2650 BPath path;
2652 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true,
2653 &bootVolume);
2654 if (result != B_OK)
2655 return result;
2657 return deskDir->SetTo(path.Path());
2661 static bool
2662 FSIsDirFlavor(const BEntry* entry, directory_which directoryType)
2664 StatStruct dir_stat;
2665 StatStruct entry_stat;
2666 BVolume volume;
2667 BPath path;
2669 if (entry->GetStat(&entry_stat) != B_OK)
2670 return false;
2672 if (volume.SetTo(entry_stat.st_dev) != B_OK)
2673 return false;
2675 if (find_directory(directoryType, &path, false, &volume) != B_OK)
2676 return false;
2678 stat(path.Path(), &dir_stat);
2680 return dir_stat.st_ino == entry_stat.st_ino
2681 && dir_stat.st_dev == entry_stat.st_dev;
2685 bool
2686 FSIsPrintersDir(const BEntry* entry)
2688 return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
2692 bool
2693 FSIsTrashDir(const BEntry* entry)
2695 return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
2699 bool
2700 FSIsDeskDir(const BEntry* entry)
2702 BPath path;
2703 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true);
2704 if (result != B_OK)
2705 return false;
2707 BEntry entryToCompare(path.Path());
2708 return entryToCompare == *entry;
2712 bool
2713 FSIsHomeDir(const BEntry* entry)
2715 return FSIsDirFlavor(entry, B_USER_DIRECTORY);
2719 bool
2720 FSIsRootDir(const BEntry* entry)
2722 BPath path(entry);
2723 return path == "/";
2727 bool
2728 DirectoryMatchesOrContains(const BEntry* entry, directory_which which)
2730 BPath path;
2731 if (find_directory(which, &path, false, NULL) != B_OK)
2732 return false;
2734 BEntry dirEntry(path.Path());
2735 if (dirEntry.InitCheck() != B_OK)
2736 return false;
2738 if (dirEntry == *entry)
2739 // root level match
2740 return true;
2742 BDirectory dir(&dirEntry);
2743 return dir.Contains(entry);
2747 bool
2748 DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath,
2749 directory_which which)
2751 BPath path;
2752 if (find_directory(which, &path, false, NULL) != B_OK)
2753 return false;
2755 path.Append(additionalPath);
2756 BEntry dirEntry(path.Path());
2757 if (dirEntry.InitCheck() != B_OK)
2758 return false;
2760 if (dirEntry == *entry)
2761 // root level match
2762 return true;
2764 BDirectory dir(&dirEntry);
2765 return dir.Contains(entry);
2769 bool
2770 DirectoryMatches(const BEntry* entry, directory_which which)
2772 BPath path;
2773 if (find_directory(which, &path, false, NULL) != B_OK)
2774 return false;
2776 BEntry dirEntry(path.Path());
2777 if (dirEntry.InitCheck() != B_OK)
2778 return false;
2780 return dirEntry == *entry;
2784 bool
2785 DirectoryMatches(const BEntry* entry, const char* additionalPath,
2786 directory_which which)
2788 BPath path;
2789 if (find_directory(which, &path, false, NULL) != B_OK)
2790 return false;
2792 path.Append(additionalPath);
2793 BEntry dirEntry(path.Path());
2794 if (dirEntry.InitCheck() != B_OK)
2795 return false;
2797 return dirEntry == *entry;
2801 extern status_t
2802 FSFindTrackerSettingsDir(BPath* path, bool autoCreate)
2804 status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path,
2805 autoCreate);
2806 if (result != B_OK)
2807 return result;
2809 path->Append("Tracker");
2811 return mkdir(path->Path(), 0777) ? B_OK : errno;
2815 bool
2816 FSInTrashDir(const entry_ref* ref)
2818 BEntry entry(ref);
2819 if (entry.InitCheck() != B_OK)
2820 return false;
2822 BDirectory trashDir;
2823 if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
2824 return false;
2826 return trashDir.Contains(&entry);
2830 void
2831 FSEmptyTrash()
2833 if (find_thread("_tracker_empty_trash_") != B_OK) {
2834 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
2835 B_NORMAL_PRIORITY, NULL));
2840 status_t
2841 empty_trash(void*)
2843 // empty trash on all mounted volumes
2844 status_t status = B_OK;
2846 TrackerCopyLoopControl loopControl(kTrashState);
2848 // calculate the sum total of all items on all volumes in trash
2849 BObjectList<entry_ref> srcList;
2850 int32 totalCount = 0;
2851 off_t totalSize = 0;
2853 BVolumeRoster volumeRoster;
2854 BVolume volume;
2855 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2856 if (volume.IsReadOnly() || !volume.IsPersistent())
2857 continue;
2859 BDirectory trashDirectory;
2860 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2861 continue;
2863 BEntry entry;
2864 trashDirectory.GetEntry(&entry);
2866 entry_ref ref;
2867 entry.GetRef(&ref);
2868 srcList.AddItem(&ref);
2869 status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(),
2870 &totalCount, &totalSize);
2871 if (status != B_OK)
2872 break;
2874 srcList.MakeEmpty();
2876 // don't count trash directory itself
2877 totalCount--;
2880 if (status == B_OK) {
2881 loopControl.Init(totalCount, totalCount);
2883 volumeRoster.Rewind();
2884 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2885 if (volume.IsReadOnly() || !volume.IsPersistent())
2886 continue;
2888 BDirectory trashDirectory;
2889 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
2890 continue;
2892 BEntry entry;
2893 trashDirectory.GetEntry(&entry);
2894 status = FSDeleteFolder(&entry, &loopControl, true, false);
2898 if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) {
2899 BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"),
2900 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2901 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2902 alert->Go();
2905 return B_OK;
2909 status_t
2910 _DeleteTask(BObjectList<entry_ref>* list, bool confirm)
2912 if (confirm) {
2913 bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
2915 if (!dontMoveToTrash) {
2916 BAlert* alert = new BAlert("",
2917 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2918 B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"),
2919 B_TRANSLATE("Delete"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
2920 B_WARNING_ALERT);
2922 alert->SetShortcut(0, B_ESCAPE);
2923 alert->SetShortcut(1, 'm');
2924 alert->SetShortcut(2, 'd');
2926 switch (alert->Go()) {
2927 case 0:
2928 delete list;
2929 return B_OK;
2930 case 1:
2931 FSMoveToTrash(list, NULL, false);
2932 return B_OK;
2934 } else {
2935 BAlert* alert = new BAlert("",
2936 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr),
2937 B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"), NULL,
2938 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
2940 alert->SetShortcut(0, B_ESCAPE);
2941 alert->SetShortcut(1, 'd');
2943 if (!alert->Go()) {
2944 delete list;
2945 return B_OK;
2950 TrackerCopyLoopControl loopControl(kDeleteState);
2952 // calculate the sum total of all items on all volumes in trash
2953 int32 totalItems = 0;
2954 int64 totalSize = 0;
2956 status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
2957 &totalSize);
2958 if (status == B_OK) {
2959 loopControl.Init(totalItems, totalItems);
2961 int32 count = list->CountItems();
2962 for (int32 index = 0; index < count; index++) {
2963 entry_ref ref(*list->ItemAt(index));
2964 BEntry entry(&ref);
2965 loopControl.UpdateStatus(ref.name, ref, 1, true);
2966 if (entry.IsDirectory())
2967 status = FSDeleteFolder(&entry, &loopControl, true, true, true);
2968 else
2969 status = entry.Remove();
2972 if (status != kTrashCanceled && status != kUserCanceled
2973 && status != B_OK) {
2974 BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"),
2975 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
2976 B_WARNING_ALERT);
2977 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2978 alert->Go();
2982 delete list;
2984 return B_OK;
2987 status_t
2988 FSRecursiveCreateFolder(BPath path)
2990 BEntry entry(path.Path());
2991 if (entry.InitCheck() != B_OK) {
2992 BPath parentPath;
2993 status_t err = path.GetParent(&parentPath);
2994 if (err != B_OK)
2995 return err;
2997 err = FSRecursiveCreateFolder(parentPath);
2998 if (err != B_OK)
2999 return err;
3002 entry.SetTo(path.Path());
3003 if (entry.Exists())
3004 return B_FILE_EXISTS;
3006 BDirectory parent;
3007 entry.GetParent(&parent);
3008 parent.CreateDirectory(entry.Name(), NULL);
3010 return B_OK;
3013 status_t
3014 _RestoreTask(BObjectList<entry_ref>* list)
3016 TrackerCopyLoopControl loopControl(kRestoreFromTrashState);
3018 // calculate the sum total of all items that will be restored
3019 int32 totalItems = 0;
3020 int64 totalSize = 0;
3022 status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems,
3023 &totalSize);
3024 if (err == B_OK) {
3025 loopControl.Init(totalItems, totalItems);
3027 int32 count = list->CountItems();
3028 for (int32 index = 0; index < count; index++) {
3029 entry_ref ref(*list->ItemAt(index));
3030 BEntry entry(&ref);
3031 BPath originalPath;
3033 loopControl.UpdateStatus(ref.name, ref, 1, true);
3035 if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
3036 continue;
3038 BEntry originalEntry(originalPath.Path());
3039 BPath parentPath;
3040 err = originalPath.GetParent(&parentPath);
3041 if (err != B_OK)
3042 continue;
3043 BEntry parentEntry(parentPath.Path());
3045 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
3046 if (FSRecursiveCreateFolder(parentPath) == B_OK) {
3047 originalEntry.SetTo(originalPath.Path());
3048 if (entry.InitCheck() != B_OK)
3049 continue;
3053 if (!originalEntry.Exists()) {
3054 BDirectory dir(parentPath.Path());
3055 if (dir.InitCheck() == B_OK) {
3056 const char* leafName = originalEntry.Name();
3057 if (entry.MoveTo(&dir, leafName) == B_OK) {
3058 BNode node(&entry);
3059 if (node.InitCheck() == B_OK)
3060 node.RemoveAttr(kAttrOriginalPath);
3065 err = loopControl.CheckUserCanceled();
3066 if (err != B_OK)
3067 break;
3071 delete list;
3073 return err;
3076 void
3077 FSCreateTrashDirs()
3079 BVolume volume;
3080 BVolumeRoster roster;
3082 roster.Rewind();
3083 while (roster.GetNextVolume(&volume) == B_OK) {
3084 if (volume.IsReadOnly() || !volume.IsPersistent())
3085 continue;
3087 BDirectory trashDir;
3088 FSGetTrashDir(&trashDir, volume.Device());
3093 status_t
3094 FSCreateNewFolder(const entry_ref* ref)
3096 node_ref node;
3097 node.device = ref->device;
3098 node.node = ref->directory;
3100 BDirectory dir(&node);
3101 status_t result = dir.InitCheck();
3102 if (result != B_OK)
3103 return result;
3105 // ToDo: is that really necessary here?
3106 BString name(ref->name);
3107 FSMakeOriginalName(name, &dir, "-");
3109 BDirectory newDir;
3110 result = dir.CreateDirectory(name.String(), &newDir);
3111 if (result != B_OK)
3112 return result;
3114 BNodeInfo nodeInfo(&newDir);
3115 nodeInfo.SetType(B_DIR_MIMETYPE);
3117 return result;
3121 status_t
3122 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef,
3123 node_ref* newNode)
3125 BDirectory dir(dirNode);
3126 status_t result = dir.InitCheck();
3127 if (result == B_OK) {
3128 char name[B_FILE_NAME_LENGTH];
3129 strlcpy(name, B_TRANSLATE("New folder"), sizeof(name));
3131 int32 fnum = 1;
3132 while (dir.Contains(name)) {
3133 // if base name already exists then add a number
3134 // ToDo:
3135 // move this logic ot FSMakeOriginalName
3136 if (++fnum > 9) {
3137 snprintf(name, sizeof(name), B_TRANSLATE("New folder%ld"),
3138 fnum);
3139 } else {
3140 snprintf(name, sizeof(name), B_TRANSLATE("New folder %ld"),
3141 fnum);
3145 BDirectory newDir;
3146 result = dir.CreateDirectory(name, &newDir);
3147 if (result == B_OK) {
3148 BEntry entry;
3149 newDir.GetEntry(&entry);
3150 entry.GetRef(newRef);
3151 entry.GetNodeRef(newNode);
3153 BNodeInfo nodeInfo(&newDir);
3154 nodeInfo.SetType(B_DIR_MIMETYPE);
3156 // add undo item
3157 NewFolderUndo undo(*newRef);
3158 return B_OK;
3162 BAlert* alert = new BAlert("",
3163 B_TRANSLATE("Sorry, could not create a new folder."),
3164 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3165 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3166 alert->Go();
3167 return result;
3171 ReadAttrResult
3172 ReadAttr(const BNode* node, const char* hostAttrName,
3173 const char* foreignAttrName, type_code type, off_t offset, void* buffer,
3174 size_t length, void (*swapFunc)(void*), bool isForeign)
3176 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer,
3177 length) == (ssize_t)length) {
3178 return kReadAttrNativeOK;
3181 // PRINT(("trying %s\n", foreignAttrName));
3182 // try the other endianness
3183 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length)
3184 != (ssize_t)length) {
3185 return kReadAttrFailed;
3188 // PRINT(("got %s\n", foreignAttrName));
3189 if (!swapFunc)
3190 return kReadAttrForeignOK;
3192 (swapFunc)(buffer);
3193 // run the endian swapper
3195 return kReadAttrForeignOK;
3199 ReadAttrResult
3200 GetAttrInfo(const BNode* node, const char* hostAttrName,
3201 const char* foreignAttrName, type_code* type, size_t* size)
3203 attr_info info;
3205 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
3206 if (type)
3207 *type = info.type;
3208 if (size)
3209 *size = (size_t)info.size;
3211 return kReadAttrNativeOK;
3214 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
3215 if (type)
3216 *type = info.type;
3217 if (size)
3218 *size = (size_t)info.size;
3220 return kReadAttrForeignOK;
3222 return kReadAttrFailed;
3226 status_t
3227 FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref)
3229 node_ref nodeRef;
3230 if (entry.GetNodeRef(&nodeRef) == B_OK) {
3231 if (VirtualDirectoryManager* manager
3232 = VirtualDirectoryManager::Instance()) {
3233 AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3234 if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref,
3235 nodeRef)) {
3236 return B_OK;
3241 status_t error;
3242 BDirectory parent;
3243 BEntry parentEntry;
3244 if ((error = entry.GetParent(&parent)) != B_OK
3245 || (error = parent.GetEntry(&parentEntry)) != B_OK
3246 || (error = parentEntry.GetRef(&_ref)) != B_OK) {
3247 return error;
3250 return B_OK;
3254 status_t
3255 FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry)
3257 node_ref nodeRef;
3258 if (entry.GetNodeRef(&nodeRef) == B_OK) {
3259 if (VirtualDirectoryManager* manager
3260 = VirtualDirectoryManager::Instance()) {
3261 AutoLocker<VirtualDirectoryManager> managerLocker(manager);
3262 entry_ref parentRef;
3263 if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef,
3264 nodeRef)) {
3265 return _entry.SetTo(&parentRef);
3270 return entry.GetParent(&_entry);
3274 status_t
3275 FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node)
3277 entry_ref ref;
3278 status_t error = FSGetParentVirtualDirectoryAware(entry, ref);
3279 if (error == B_OK)
3280 error = _node.SetTo(&ref);
3281 return error;
3285 // launching code
3287 static status_t
3288 TrackerOpenWith(const BMessage* refs)
3290 BMessage clone(*refs);
3292 ASSERT(dynamic_cast<TTracker*>(be_app) != NULL);
3293 ASSERT(clone.what != 0);
3295 clone.AddInt32("launchUsingSelector", 0);
3296 // runs the Open With window
3297 be_app->PostMessage(&clone);
3299 return B_OK;
3303 static void
3304 AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on),
3305 const entry_ref* appRef, const BMessage* refs, bool openWithOK)
3307 BMessage* task = new BMessage;
3308 task->AddPointer("function", (void*)func);
3309 task->AddMessage("refs", refs);
3310 task->AddBool("openWithOK", openWithOK);
3311 if (appRef != NULL)
3312 task->AddRef("appRef", appRef);
3314 extern BLooper* gLaunchLooper;
3315 gLaunchLooper->PostMessage(task);
3319 static bool
3320 SniffIfGeneric(const entry_ref* ref)
3322 BNode node(ref);
3323 char type[B_MIME_TYPE_LENGTH];
3324 BNodeInfo info(&node);
3325 if (info.GetType(type) == B_OK
3326 && strcasecmp(type, B_FILE_MIME_TYPE) != 0) {
3327 // already has a type and it's not octet stream
3328 return false;
3331 BPath path(ref);
3332 if (path.Path()) {
3333 // force a mimeset
3334 node.RemoveAttr(kAttrMIMEType);
3335 update_mime_info(path.Path(), 0, 1, 1);
3338 return true;
3342 static void
3343 SniffIfGeneric(const BMessage* refs)
3345 entry_ref ref;
3346 for (int32 index = 0; ; index++) {
3347 if (refs->FindRef("refs", index, &ref) != B_OK)
3348 break;
3349 SniffIfGeneric(&ref);
3354 static void
3355 _TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs,
3356 bool openWithOK)
3358 team_id team;
3360 status_t error = B_ERROR;
3361 BString alertString;
3363 for (int32 mimesetIt = 0; ; mimesetIt++) {
3364 error = be_roster->Launch(appRef, refs, &team);
3365 if (error == B_ALREADY_RUNNING)
3366 // app already running, not really an error
3367 error = B_OK;
3369 if (error == B_OK)
3370 break;
3372 if (mimesetIt > 0)
3373 break;
3375 // failed to open, try mimesetting the refs and launching again
3376 SniffIfGeneric(refs);
3379 if (error == B_OK) {
3380 // close possible parent window, if specified
3381 const node_ref* nodeToClose = 0;
3382 ssize_t numBytes;
3383 if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE,
3384 (const void**)&nodeToClose, &numBytes) == B_OK
3385 && nodeToClose != NULL) {
3386 TTracker* tracker = dynamic_cast<TTracker*>(be_app);
3387 if (tracker != NULL)
3388 tracker->CloseParent(*nodeToClose);
3390 } else {
3391 alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). "));
3392 alertString.ReplaceFirst("%name", appRef->name);
3393 alertString.ReplaceFirst("%error", strerror(error));
3394 if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) {
3395 alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3396 BAlert* alert = new BAlert("", alertString.String(),
3397 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3398 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3399 alert->SetShortcut(0, B_ESCAPE);
3400 if (alert->Go() == 1)
3401 error = TrackerOpenWith(refs);
3402 } else {
3403 BAlert* alert = new BAlert("", alertString.String(),
3404 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3405 B_WARNING_ALERT);
3406 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3407 alert->Go();
3412 extern "C" char** environ;
3415 static status_t
3416 LoaderErrorDetails(const entry_ref* app, BString &details)
3418 BPath path;
3419 BEntry appEntry(app, true);
3421 status_t result = appEntry.GetPath(&path);
3422 if (result != B_OK)
3423 return result;
3425 char* argv[2] = { const_cast<char*>(path.Path()), 0};
3427 port_id errorPort = create_port(1, "Tracker loader error");
3429 // count environment variables
3430 int32 envCount = 0;
3431 while (environ[envCount] != NULL)
3432 envCount++;
3434 char** flatArgs = NULL;
3435 size_t flatArgsSize;
3436 result = __flatten_process_args((const char**)argv, 1,
3437 environ, &envCount, argv[0], &flatArgs, &flatArgsSize);
3438 if (result != B_OK)
3439 return result;
3441 result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
3442 B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
3443 if (result == B_OK) {
3444 // we weren't supposed to be able to start the application...
3445 return B_ERROR;
3448 // read error message from port and construct details string
3450 ssize_t bufferSize;
3452 do {
3453 bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
3454 } while (bufferSize == B_INTERRUPTED);
3456 if (bufferSize <= B_OK) {
3457 delete_port(errorPort);
3458 return bufferSize;
3461 uint8* buffer = (uint8*)malloc(bufferSize);
3462 if (buffer == NULL) {
3463 delete_port(errorPort);
3464 return B_NO_MEMORY;
3467 bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
3468 B_RELATIVE_TIMEOUT, 0);
3469 delete_port(errorPort);
3471 if (bufferSize < B_OK) {
3472 free(buffer);
3473 return bufferSize;
3476 BMessage message;
3477 result = message.Unflatten((const char*)buffer);
3478 free(buffer);
3480 if (result != B_OK)
3481 return result;
3483 int32 errorCode = B_ERROR;
3484 result = message.FindInt32("error", &errorCode);
3485 if (result != B_OK)
3486 return result;
3488 const char* detailName = NULL;
3489 switch (errorCode) {
3490 case B_MISSING_LIBRARY:
3491 detailName = "missing library";
3492 break;
3494 case B_MISSING_SYMBOL:
3495 detailName = "missing symbol";
3496 break;
3499 if (detailName == NULL)
3500 return B_ERROR;
3502 const char* detail;
3503 for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK;
3504 i++) {
3505 if (i > 0)
3506 details += ", ";
3507 details += detail;
3510 return B_OK;
3514 static void
3515 _TrackerLaunchDocuments(const entry_ref*, const BMessage* refs,
3516 bool openWithOK)
3518 if (refs == NULL)
3519 return;
3521 BMessage copyOfRefs(*refs);
3523 entry_ref documentRef;
3524 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) {
3525 // nothing to launch, we are done
3526 return;
3529 status_t error = B_ERROR;
3530 entry_ref app;
3531 BMessage* refsToPass = NULL;
3532 BString alertString;
3533 const char* alternative = 0;
3535 for (int32 mimesetIt = 0; ; mimesetIt++) {
3536 alertString = "";
3537 error = be_roster->FindApp(&documentRef, &app);
3539 if (error != B_OK && mimesetIt == 0) {
3540 SniffIfGeneric(&copyOfRefs);
3541 continue;
3544 if (error != B_OK) {
3545 alertString.SetTo(B_TRANSLATE("Could not find an application to "
3546 "open \"%name\" (%error). "));
3547 alertString.ReplaceFirst("%name", documentRef.name);
3548 alertString.ReplaceFirst("%error", strerror(error));
3549 if (openWithOK)
3550 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3552 break;
3553 } else {
3554 BEntry appEntry(&app, true);
3555 for (int32 index = 0;;) {
3556 // remove the app itself from the refs received so we don't
3557 // try to open ourselves
3558 entry_ref ref;
3559 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
3560 break;
3562 // deal with symlinks properly
3563 BEntry documentEntry(&ref, true);
3564 if (appEntry == documentEntry) {
3565 PRINT(("stripping %s, app %s \n", ref.name, app.name));
3566 copyOfRefs.RemoveData("refs", index);
3567 } else {
3568 PRINT(("leaving %s, app %s \n", ref.name, app.name));
3569 index++;
3573 refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
3574 team_id team;
3575 error = be_roster->Launch(&app, refsToPass, &team);
3576 if (error == B_ALREADY_RUNNING)
3577 // app already running, not really an error
3578 error = B_OK;
3579 if (error == B_OK || mimesetIt != 0)
3580 break;
3582 SniffIfGeneric(&copyOfRefs);
3586 if (error != B_OK && alertString.Length() == 0) {
3587 BString loaderErrorString;
3588 bool openedDocuments = true;
3590 if (!refsToPass) {
3591 // we just double clicked the app itself, do not offer to
3592 // find a handling app
3593 openWithOK = false;
3594 openedDocuments = false;
3596 if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) {
3597 // We know it's an executable, but something unsupported
3598 alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported "
3599 "executable."));
3600 alertString.ReplaceFirst("%name", app.name);
3601 } else if (error == B_LEGACY_EXECUTABLE && !refsToPass) {
3602 // For the moment, this marks an old R3 binary, we may want to
3603 // extend it to gcc2 binaries someday post R1
3604 alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. "
3605 "Please obtain an updated version or recompile "
3606 "the application."));
3607 alertString.ReplaceFirst("%name", app.name);
3608 } else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
3609 alertString.SetTo(B_TRANSLATE("Could not open \"%name\". "
3610 "The file is mistakenly marked as executable. "));
3611 alertString.ReplaceFirst("%name", app.name);
3613 if (!openWithOK) {
3614 // offer the possibility to change the permissions
3616 alertString << B_TRANSLATE("\nShould this be fixed?");
3617 BAlert* alert = new BAlert("", alertString.String(),
3618 B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0,
3619 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3620 alert->SetShortcut(0, B_ESCAPE);
3621 if (alert->Go() == 1) {
3622 BEntry entry(&documentRef);
3623 mode_t permissions;
3625 error = entry.GetPermissions(&permissions);
3626 if (error == B_OK) {
3627 error = entry.SetPermissions(permissions
3628 & ~(S_IXUSR | S_IXGRP | S_IXOTH));
3630 if (error == B_OK) {
3631 // we updated the permissions, so let's try again
3632 _TrackerLaunchDocuments(NULL, refs, false);
3633 return;
3634 } else {
3635 alertString.SetTo(B_TRANSLATE("Could not update "
3636 "permissions of file \"%name\". %error"));
3637 alertString.ReplaceFirst("%name", app.name);
3638 alertString.ReplaceFirst("%error", strerror(error));
3640 } else
3641 return;
3644 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr);
3645 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
3646 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3647 "because application \"%app\" is in the Trash. "));
3648 alertString.ReplaceFirst("%document", documentRef.name);
3649 alertString.ReplaceFirst("%app", app.name);
3650 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3651 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
3652 alertString.SetTo(
3653 B_TRANSLATE("Could not open \"%name\" (%error). "));
3654 alertString.ReplaceFirst("%name", documentRef.name);
3655 alertString.ReplaceFirst("%error", strerror(error));
3656 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3657 } else if (error == B_MISSING_SYMBOL
3658 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3659 if (openedDocuments) {
3660 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3661 "with application \"%app\" (Missing symbol: %symbol). "
3662 "\n"));
3663 alertString.ReplaceFirst("%document", documentRef.name);
3664 alertString.ReplaceFirst("%app", app.name);
3665 alertString.ReplaceFirst("%symbol",
3666 loaderErrorString.String());
3667 } else {
3668 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3669 "(Missing symbol: %symbol). \n"));
3670 alertString.ReplaceFirst("%document", documentRef.name);
3671 alertString.ReplaceFirst("%symbol",
3672 loaderErrorString.String());
3674 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3675 } else if (error == B_MISSING_LIBRARY
3676 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
3677 if (openedDocuments) {
3678 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3679 "with application \"%app\" (Missing libraries: %library). "
3680 "\n"));
3681 alertString.ReplaceFirst("%document", documentRef.name);
3682 alertString.ReplaceFirst("%app", app.name);
3683 alertString.ReplaceFirst("%library",
3684 loaderErrorString.String());
3685 } else {
3686 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" "
3687 "(Missing libraries: %library). \n"));
3688 alertString.ReplaceFirst("%document", documentRef.name);
3689 alertString.ReplaceFirst("%library",
3690 loaderErrorString.String());
3692 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3693 } else {
3694 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with "
3695 "application \"%app\" (%error). "));
3696 alertString.ReplaceFirst("%document", documentRef.name);
3697 alertString.ReplaceFirst("%app", app.name);
3698 alertString.ReplaceFirst("%error", strerror(error));
3699 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr);
3703 if (error != B_OK) {
3704 if (openWithOK) {
3705 ASSERT(alternative);
3706 alertString << alternative;
3707 BAlert* alert = new BAlert("", alertString.String(),
3708 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0,
3709 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3710 alert->SetShortcut(0, B_ESCAPE);
3711 if (alert->Go() == 1)
3712 error = TrackerOpenWith(refs);
3713 } else {
3714 BAlert* alert = new BAlert("", alertString.String(),
3715 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL,
3716 B_WARNING_ALERT);
3717 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3718 alert->Go();
3723 // the following three calls don't return any reasonable error codes,
3724 // should fix that, making them void
3726 status_t
3727 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async,
3728 bool openWithOK)
3730 if (!async)
3731 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
3732 else {
3733 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs,
3734 openWithOK);
3737 return B_OK;
3740 status_t
3741 TrackerLaunch(const entry_ref* appRef, bool async)
3743 if (!async)
3744 _TrackerLaunchAppWithDocuments(appRef, NULL, false);
3745 else
3746 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
3748 return B_OK;
3751 status_t
3752 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK)
3754 if (!async)
3755 _TrackerLaunchDocuments(NULL, refs, openWithOK);
3756 else
3757 AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK);
3759 return B_OK;
3762 status_t
3763 LaunchBrokenLink(const char* signature, const BMessage* refs)
3765 // This call is to support a hacky workaround for double-clicking
3766 // broken refs for cifs
3767 be_roster->Launch(signature, const_cast<BMessage*>(refs));
3768 return B_OK;
3772 // external launch calls; need to be robust, work if Tracker is not running
3775 #if !B_BEOS_VERSION_DANO
3776 _IMPEXP_TRACKER
3777 #endif
3778 status_t
3779 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived,
3780 bool async, bool openWithOK)
3782 return TrackerLaunch(application, refsReceived, async, openWithOK);
3786 #if !B_BEOS_VERSION_DANO
3787 _IMPEXP_TRACKER
3788 #endif
3789 status_t
3790 FSOpenWith(BMessage* listOfRefs)
3792 status_t result = B_ERROR;
3793 listOfRefs->what = B_REFS_RECEIVED;
3795 if (dynamic_cast<TTracker*>(be_app) != NULL)
3796 result = TrackerOpenWith(listOfRefs);
3797 else
3798 ASSERT(!"not yet implemented");
3800 return result;
3804 // legacy calls, need for compatibility
3807 void
3808 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents)
3810 TrackerLaunch(executable, documents, true);
3811 delete documents;
3815 status_t
3816 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs)
3818 BMessage temp(B_REFS_RECEIVED);
3819 if (listOfRefs == NULL) {
3820 ASSERT(ref != NULL);
3821 temp.AddRef("refs", ref);
3822 listOfRefs = &temp;
3824 FSOpenWith(listOfRefs);
3826 return B_OK;
3830 status_t
3831 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async)
3833 if (refs != NULL)
3834 refs->what = B_REFS_RECEIVED;
3836 status_t result = TrackerLaunch(appRef, refs, async, true);
3837 delete refs;
3839 return result;
3843 void
3844 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace)
3846 FSLaunchItem(appRef, refs, workspace, true);
3850 // Get the original path of an entry in the trash
3851 status_t
3852 FSGetOriginalPath(BEntry* entry, BPath* result)
3854 status_t err;
3855 entry_ref ref;
3856 err = entry->GetRef(&ref);
3857 if (err != B_OK)
3858 return err;
3860 // Only call the routine for entries in the trash
3861 if (!FSInTrashDir(&ref))
3862 return B_ERROR;
3864 BNode node(entry);
3865 BString originalPath;
3866 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
3867 // We're in luck, the entry has the original path in an attribute
3868 err = result->SetTo(originalPath.String());
3869 return err;
3872 // Iterate the parent directories to find one with
3873 // the original path attribute
3874 BEntry parent(*entry);
3875 err = parent.InitCheck();
3876 if (err != B_OK)
3877 return err;
3879 // walk up the directory structure until we find a node
3880 // with original path attribute
3881 do {
3882 // move to the parent of this node
3883 err = parent.GetParent(&parent);
3884 if (err != B_OK)
3885 return err;
3887 // return if we are at the root of the trash
3888 if (FSIsTrashDir(&parent))
3889 return B_ENTRY_NOT_FOUND;
3891 // get the parent as a node
3892 err = node.SetTo(&parent);
3893 if (err != B_OK)
3894 return err;
3895 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
3897 // Found the attribute, figure out there this file
3898 // used to live, based on the successfully-read attribute
3899 err = result->SetTo(originalPath.String());
3900 if (err != B_OK)
3901 return err;
3903 BPath path, pathParent;
3904 err = parent.GetPath(&pathParent);
3905 if (err != B_OK)
3906 return err;
3908 err = entry->GetPath(&path);
3909 if (err != B_OK)
3910 return err;
3912 result->Append(path.Path() + strlen(pathParent.Path()) + 1);
3913 // compute the new path by appending the offset of
3914 // the item we are locating, to the original path
3915 // of the parent
3917 return B_OK;
3921 directory_which
3922 WellKnowEntryList::Match(const node_ref* node)
3924 const WellKnownEntry* result = MatchEntry(node);
3925 if (result != NULL)
3926 return result->which;
3928 return (directory_which)-1;
3932 const WellKnowEntryList::WellKnownEntry*
3933 WellKnowEntryList::MatchEntry(const node_ref* node)
3935 if (self == NULL)
3936 self = new WellKnowEntryList();
3938 return self->MatchEntryCommon(node);
3942 const WellKnowEntryList::WellKnownEntry*
3943 WellKnowEntryList::MatchEntryCommon(const node_ref* node)
3945 uint32 count = entries.size();
3946 for (uint32 index = 0; index < count; index++) {
3947 if (*node == entries[index].node)
3948 return &entries[index];
3951 return NULL;
3955 void
3956 WellKnowEntryList::Quit()
3958 delete self;
3959 self = NULL;
3963 void
3964 WellKnowEntryList::AddOne(directory_which which, const char* name)
3966 BPath path;
3967 if (find_directory(which, &path, true) != B_OK)
3968 return;
3970 BEntry entry(path.Path(), true);
3971 node_ref node;
3972 if (entry.GetNodeRef(&node) != B_OK)
3973 return;
3975 entries.push_back(WellKnownEntry(&node, which, name));
3979 void
3980 WellKnowEntryList::AddOne(directory_which which, directory_which base,
3981 const char* extra, const char* name)
3983 BPath path;
3984 if (find_directory(base, &path, true) != B_OK)
3985 return;
3987 path.Append(extra);
3988 BEntry entry(path.Path(), true);
3989 node_ref node;
3990 if (entry.GetNodeRef(&node) != B_OK)
3991 return;
3993 entries.push_back(WellKnownEntry(&node, which, name));
3997 void
3998 WellKnowEntryList::AddOne(directory_which which, const char* path,
3999 const char* name)
4001 BEntry entry(path, true);
4002 node_ref node;
4003 if (entry.GetNodeRef(&node) != B_OK)
4004 return;
4006 entries.push_back(WellKnownEntry(&node, which, name));
4010 WellKnowEntryList::WellKnowEntryList()
4012 AddOne(B_SYSTEM_DIRECTORY, "system");
4013 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
4014 AddOne(B_USER_DIRECTORY, "home");
4016 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
4017 AddOne(B_USER_FONTS_DIRECTORY, "fonts");
4019 AddOne(B_BEOS_APPS_DIRECTORY, "apps");
4020 AddOne(B_APPS_DIRECTORY, "apps");
4021 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY,
4022 B_USER_DESKBAR_DIRECTORY, "Applications", "apps");
4024 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
4025 AddOne(B_PREFERENCES_DIRECTORY, "preferences");
4026 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY,
4027 B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences");
4029 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail",
4030 "mail");
4032 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY,
4033 "queries", "queries");
4035 AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop");
4036 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY,
4037 B_USER_DESKBAR_DIRECTORY, "Development", "develop");
4039 AddOne(B_USER_CONFIG_DIRECTORY, "config");
4041 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY,
4042 "people", "people");
4044 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY,
4045 "downloads", "downloads");
4048 WellKnowEntryList* WellKnowEntryList::self = NULL;
4050 } // namespace BPrivate