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
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.
54 #include <Application.h>
57 #include <Directory.h>
59 #include <FindDirectory.h>
61 #include <MessageFormat.h>
69 #include <VolumeRoster.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"
82 #include "FSUndoRedo.h"
84 #include "InfoWindow.h"
85 #include "MimeTypes.h"
86 #include "OverrideAlert.h"
87 #include "StatusWindow.h"
90 #include "TrackerSettings.h"
91 #include "Utilities.h"
92 #include "VirtualDirectoryManager.h"
96 kUserCanceled
= B_ERRORS_END
+ 1,
97 kCopyCanceled
= kUserCanceled
,
101 enum ConflictCheckResult
{
102 kCanceled
= kUserCanceled
,
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
,
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
,
142 bool DirectoryMatches(const BEntry
*, directory_which
);
143 bool DirectoryMatches(const BEntry
*, const char* additionalPath
,
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"
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 "
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\""
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 "
192 static const char* kFindAlternativeStr
=
193 B_TRANSLATE_MARK("Would you like to find some other suitable "
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
[] = {
208 // #pragma mark - CopyLoopControl
211 CopyLoopControl::~CopyLoopControl()
217 CopyLoopControl::Init(uint32 jobKind
)
223 CopyLoopControl::Init(int32 totalItems
, off_t totalSize
,
224 const entry_ref
* destDir
, bool showCount
)
230 CopyLoopControl::FileError(const char* message
, const char* name
,
231 status_t error
, bool allowContinue
)
238 CopyLoopControl::UpdateStatus(const char* name
, const entry_ref
& ref
,
239 int32 count
, bool optional
)
245 CopyLoopControl::CheckUserCanceled()
251 CopyLoopControl::OverwriteMode
252 CopyLoopControl::OverwriteOnConflict(const BEntry
* srcEntry
,
253 const char* destName
, const BDirectory
* destDir
, bool srcIsDir
,
261 CopyLoopControl::SkipEntry(const BEntry
*, bool)
263 // Tracker makes no exceptions
269 CopyLoopControl::ChecksumChunk(const char*, size_t)
275 CopyLoopControl::ChecksumFile(const entry_ref
*)
282 CopyLoopControl::SkipAttribute(const char*)
289 CopyLoopControl::PreserveAttribute(const char*)
295 // #pragma mark - TrackerCopyLoopControl
298 TrackerCopyLoopControl::TrackerCopyLoopControl()
300 fThread(find_thread(NULL
)),
306 TrackerCopyLoopControl::TrackerCopyLoopControl(uint32 jobKind
)
308 fThread(find_thread(NULL
)),
315 TrackerCopyLoopControl::TrackerCopyLoopControl(int32 totalItems
,
318 fThread(find_thread(NULL
)),
321 Init(totalItems
, totalSize
);
325 TrackerCopyLoopControl::~TrackerCopyLoopControl()
327 if (gStatusWindow
!= NULL
)
328 gStatusWindow
->RemoveStatusItem(fThread
);
333 TrackerCopyLoopControl::Init(uint32 jobKind
)
335 if (gStatusWindow
!= NULL
)
336 gStatusWindow
->CreateStatusItem(fThread
, (StatusWindowState
)jobKind
);
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
,
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
));
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
);
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
);
384 TrackerCopyLoopControl::CheckUserCanceled()
386 if (gStatusWindow
== NULL
)
389 if (gStatusWindow
->CheckCanceledOrPaused(fThread
))
392 if (fSourceList
!= NULL
) {
393 // TODO: Check if the user dropped additional files onto this job.
394 // printf("%p->CheckUserCanceled()\n", this);
402 TrackerCopyLoopControl::SkipAttribute(const char* attributeName
)
404 for (const char** skipAttribute
= kSkipAttributes
; *skipAttribute
;
406 if (strcmp(*skipAttribute
, attributeName
) == 0)
415 TrackerCopyLoopControl::SetSourceList(EntryList
* list
)
421 // #pragma mark - the rest
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
;
435 statBuf
= &localStatbuf
;
436 if (entry
->GetStat(statBuf
) != B_OK
)
440 if (S_ISREG(statBuf
->st_mode
))
441 return new BFile(entry
, O_RDWR
);
443 return new BNode(entry
);
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
;
459 FSSetPoseLocation(ino_t destDirInode
, BNode
* destNode
, BPoint point
)
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
))
477 FSSetPoseLocation(BEntry
* entry
, BPoint point
)
480 status_t result
= node
.InitCheck();
485 result
= entry
->GetParent(&parent
);
489 node_ref destNodeRef
;
490 result
= parent
.GetNodeRef(&destNodeRef
);
494 return FSSetPoseLocation(destNodeRef
.node
, &node
, point
);
499 FSGetPoseLocation(const BNode
* node
, BPoint
* point
)
502 if (ReadAttr(node
, kAttrPoseInfo
, kAttrPoseInfoForeign
,
503 B_RAW_TYPE
, 0, &poseInfo
, sizeof(poseInfo
), &PoseInfo::EndianSwap
)
504 == kReadAttrFailed
) {
508 if (poseInfo
.fInitedDirectory
== -1LL)
511 *point
= poseInfo
.fLocation
;
518 SetUpPoseLocation(ino_t sourceParentIno
, ino_t destParentIno
,
519 const BNode
* sourceNode
, BNode
* destNode
, BPoint
* loc
)
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
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
536 // should push all this logic to upper levels
537 FSSetPoseLocation(destParentIno
, destNode
, *loc
);
543 FSMoveToFolder(BObjectList
<entry_ref
>* srcList
, BEntry
* destEntry
,
544 uint32 moveMode
, BList
* pointList
)
546 if (srcList
->IsEmpty()) {
553 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY
, MoveTask
, srcList
,
554 destEntry
, pointList
, moveMode
);
559 FSDelete(entry_ref
* ref
, bool async
, bool confirm
)
561 BObjectList
<entry_ref
>* list
= new BObjectList
<entry_ref
>(1, true);
563 FSDeleteRefList(list
, async
, confirm
);
568 FSDeleteRefList(BObjectList
<entry_ref
>* list
, bool async
, bool confirm
)
571 LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY
, _DeleteTask
, list
,
574 _DeleteTask(list
, confirm
);
579 FSRestoreRefList(BObjectList
<entry_ref
>* list
, bool async
)
582 LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY
, _RestoreTask
,
590 FSMoveToTrash(BObjectList
<entry_ref
>* srcList
, BList
* pointList
, bool async
)
592 if (srcList
->IsEmpty()) {
599 LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY
, MoveTask
, srcList
,
600 (BEntry
*)0, pointList
, kMoveSelectionTo
);
602 MoveTask(srcList
, 0, pointList
, kMoveSelectionTo
);
607 IsDisksWindowIcon(BEntry
* entry
)
610 if (entry
->InitCheck() != B_OK
|| entry
->GetPath(&path
) != B_OK
)
613 return strcmp(path
.Path(), "/") == 0;
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
)
635 if (FSIsDeskDir(entry
) || FSIsTrashDir(entry
) || FSIsRootDir(entry
))
638 if ((!DirectoryMatchesOrContains(entry
, B_SYSTEM_DIRECTORY
)
639 && !DirectoryMatchesOrContains(entry
, B_USER_DIRECTORY
))
640 || DirectoryMatchesOrContains(entry
, B_SYSTEM_TEMP_DIRECTORY
))
645 bool requireOverride
= true;
647 if (DirectoryMatchesOrContains(entry
, B_SYSTEM_DIRECTORY
)) {
648 if (action
== kRename
) {
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 "
655 } else if(action
== kMove
) {
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 "
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
) {
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
) {
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\"."));
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
) {
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
) {
698 B_TRANSLATE("If you move %target, %osName may not behave "
699 "properly!\n\nAre you sure you want to do this?"));
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;
720 // It was not a special directory/file after all. Allow renaming.
729 if (confirmedAlready
&& *confirmedAlready
== kConfirmedHomeMove
731 // we already warned about moving home this time around
735 if (uname(&name
) == -1)
736 warning
.ReplaceFirst("%osName", "Haiku");
738 warning
.ReplaceFirst("%osName", name
.sysname
);
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");
746 buttonLabel
= B_TRANSLATE_COMMENT("I know what I'm doing",
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
;
760 if (confirmedAlready
) {
761 if (!requireOverride
)
762 *confirmedAlready
= kConfirmedHomeMove
;
764 *confirmedAlready
= kConfirmedAll
;
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
);
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
)) {
794 if (moveMode
== kCreateLink
) {
796 B_TRANSLATE("You cannot create a link to the root "
800 B_TRANSLATE("You cannot copy or move the root "
804 BAlert
* alert
= new BAlert("", errorStr
.String(),
805 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL
,
807 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
811 if (moveMode
== kMoveSelectionTo
812 && !ConfirmChangeIfWellKnownDirectory(&entry
, kMove
,
813 false, &askOnceOnly
)) {
818 if (preflightNameCheck
) {
819 ASSERT(collisionCount
);
820 ASSERT(preflightResult
);
822 *preflightResult
= kPrompt
;
825 *preflightResult
= PreFlightNameCheck(srcList
, destDir
,
826 collisionCount
, moveMode
);
827 if (*preflightResult
== kCanceled
) {
833 // set up the status display
835 case kCopySelectionTo
:
836 case kDuplicateSelection
:
837 case kMoveSelectionTo
:
839 loopControl
->Init(moveMode
== kMoveSelectionTo
? kMoveState
842 int32 totalItems
= 0;
844 if (needSizeCalculation
) {
845 if (CalcItemsAndSize(loopControl
, srcList
,
846 dstVol
->BlockSize(), &totalItems
, &totalSize
)
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
);
863 loopControl
->Init(totalItems
, totalSize
, destRef
);
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
);
882 // get rid of this cruft
884 delete_ref(void* ref
)
886 delete (entry_ref
*)ref
;
892 delete_point(void* point
)
894 delete (BPoint
*)point
;
900 MoveTask(BObjectList
<entry_ref
>* srcList
, BEntry
* destEntry
, BList
* pointList
,
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
;
912 BVolume
volume(srcVolumeDevice
);
915 bool destIsTrash
= false;
917 BDirectory
* destDirToCheck
= NULL
;
918 bool needPreflightNameCheck
= false;
919 bool sourceIsReadOnly
= volume
.IsReadOnly();
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
) {
940 entry
.SetTo(srcList
->FirstItem());
941 entry
.GetParent(&destDir
);
942 volume
.SetTo(srcVolumeDevice
);
947 FSGetTrashDir(&destDir
, srcVolumeDevice
);
948 volume
.SetTo(srcVolumeDevice
);
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
)
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
);
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
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
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
1017 && (srcRef
->device
== destRef
.device
1018 && srcRef
->directory
== deststat
.st_ino
)) {
1022 if (loopControl
.CheckUserCanceled())
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
,
1032 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1037 // are we moving item to trash?
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. "
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
,
1051 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
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);
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
) {
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
));
1088 loc
= (BPoint
*)pointList
->ItemAt(i
);
1090 result
= MoveItem(&sourceEntry
, &destDir
, loc
, moveMode
, NULL
,
1091 undo
, &loopControl
);
1097 // duplicates of srcList, destFolder were created - dispose them
1101 // delete file location list and all Points within
1102 if (pointList
!= NULL
) {
1103 pointList
->DoForEach(delete_point
);
1111 class FailWithAlert
{
1113 static void FailOnError(status_t error
, const char* string
,
1114 const char* name
= NULL
)
1117 throw FailWithAlert(error
, string
, name
);
1120 FailWithAlert(status_t error
, const char* string
, const char* name
)
1128 const char* fString
;
1136 static void FailOnError(status_t error
)
1139 throw MoveError(error
);
1142 MoveError(status_t error
)
1153 CopyFile(BEntry
* srcFile
, StatStruct
* srcStat
, BDirectory
* destDir
,
1154 CopyLoopControl
* loopControl
, BPoint
* loc
, bool makeOriginalName
,
1157 if (loopControl
->SkipEntry(srcFile
, true))
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
);
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
,
1189 case TrackerCopyLoopControl::kSkip
:
1190 // we are about to ignore this entire directory
1193 case TrackerCopyLoopControl::kReplace
:
1194 if (!conflictingEntry
.IsDirectory()) {
1195 ThrowOnError(conflictingEntry
.Remove());
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.
1207 LowLevelCopy(srcFile
, srcStat
, destDir
, destName
, loopControl
, loc
);
1208 } catch (status_t err
) {
1209 if (err
== kCopyCanceled
)
1210 throw (status_t
)err
;
1213 if (!loopControl
->FileError(
1214 B_TRANSLATE_NOCOLLECT(kFileErrorString
), destName
, err
,
1216 throw (status_t
)err
;
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
1228 CreateFileSystemCompatibleName(const BDirectory
* destDir
, char* destName
)
1230 // Is it a FAT32 file system?
1231 // (this is the only one we currently know about)
1234 destDir
->GetEntry(&target
);
1235 entry_ref targetRef
;
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';
1251 char* invalid
= destName
;
1252 while ((invalid
= strpbrk(invalid
, "?<>\\:\"|*")) != NULL
) {
1266 LowLevelCopy(BEntry
* srcEntry
, StatStruct
* srcStat
, BDirectory
* destDir
,
1267 char* destName
, CopyLoopControl
* loopControl
, BPoint
* loc
)
1270 ThrowOnError(srcEntry
->GetRef(&ref
));
1272 if (S_ISLNK(srcStat
->st_mode
)) {
1273 // handle symbolic links
1276 char linkpath
[MAXPATHLEN
];
1278 ThrowOnError(srcLink
.SetTo(srcEntry
));
1279 ssize_t size
= srcLink
.ReadLink(linkpath
, MAXPATHLEN
- 1);
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
,
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
);
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
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
);
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
,
1345 char* buffer
= new char[bufsize
];
1347 // copy data portion of file
1349 if (loopControl
->CheckUserCanceled()) {
1350 // if copy was canceled, remove partial destination file
1354 if (destDir
->FindEntry(destName
, &destEntry
) == B_OK
)
1357 throw (status_t
)kCopyCanceled
;
1361 ssize_t bytes
= srcFile
.Read(buffer
, bufsize
);
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
,
1380 } else if (bytes
< 0) {
1382 throw (status_t
)bytes
;
1389 CopyAttributes(loopControl
, &srcFile
, &destFile
, buffer
, bufsize
);
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
);
1403 if (!loopControl
->ChecksumFile(&ref
)) {
1404 // File no good. Remove and quit.
1408 if (destDir
->FindEntry(destName
, &destEntry
) == B_OK
)
1410 throw (status_t
)kUserCanceled
;
1416 CopyAttributes(CopyLoopControl
* control
, BNode
* srcNode
, BNode
* destNode
,
1417 void* buffer
, size_t bufsize
)
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();
1428 while (srcNode
->GetNextAttrName(name
) == B_OK
) {
1429 // Check to see if this attribute should be skipped.
1430 if (control
->SkipAttribute(name
))
1434 if (srcNode
->GetAttrInfo(name
, &info
) != B_OK
)
1437 // Check to see if this attribute should be overwritten when it
1439 if (control
->PreserveAttribute(name
)) {
1440 attr_info dest_info
;
1441 if (destNode
->GetAttrInfo(name
, &dest_info
) == B_OK
)
1445 // Special case for a size 0 attribute. It wouldn't be written at all
1448 destNode
->WriteAttr(name
, info
.type
, 0, buffer
, 0);
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
,
1463 destNode
->WriteAttr(name
, info
.type
, offset
, buffer
,
1473 CopyFolder(BEntry
* srcEntry
, BDirectory
* destDir
,
1474 CopyLoopControl
* loopControl
, BPoint
* loc
, bool makeOriginalName
,
1475 Undo
&undo
, bool removeSource
= false)
1479 status_t err
= B_OK
;
1480 bool createDirectory
= true;
1481 BEntry existingEntry
;
1483 if (loopControl
->SkipEntry(srcEntry
, false))
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
1513 case TrackerCopyLoopControl::kReplace
:
1515 // conflicting with a file or symbolic link, remove entry
1516 ThrowOnError(existingEntry
.Remove());
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;
1529 // loop through everything in src folder and copy it to new folder
1530 BDirectory
srcDir(srcEntry
);
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
);
1544 if (!loopControl
->FileError(B_TRANSLATE_NOCOLLECT(
1545 kFolderErrorString
), destName
, err
, true)) {
1549 // will allow rest of copy to continue
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
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
,
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
));
1588 CopyFolder(&entry
, &newDir
, loopControl
, 0, false, undo
,
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
);
1597 // Ignore special files
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());
1617 entry
->GetRef(&ref
);
1618 BDirectory
source(&ref
);
1619 if (source
.InitCheck() == B_OK
) {
1622 while (source
.GetNextEntry(¤t
) == B_OK
) {
1623 if (current
.IsDirectory()) {
1624 RecursiveMove(¤t
, &subDir
, loopControl
);
1627 name
= current
.Name();
1628 if (loopControl
->OverwriteOnConflict(¤t
, name
,
1629 &subDir
, true, false)
1630 != TrackerCopyLoopControl::kSkip
) {
1631 MoveError::FailOnError(current
.MoveTo(&subDir
,
1639 MoveError::FailOnError(entry
->MoveTo(destDir
));
1645 MoveItem(BEntry
* entry
, BDirectory
* destDir
, BPoint
* loc
, uint32 moveMode
,
1646 const char* newName
, Undo
&undo
, CopyLoopControl
* loopControl
)
1652 MoveError::FailOnError(entry
->GetStat(&statbuf
));
1653 MoveError::FailOnError(entry
->GetRef(&ref
));
1654 MoveError::FailOnError(destDir
->GetNodeRef(&destNode
));
1656 if (moveMode
== kCreateLink
|| moveMode
== kCreateRelativeLink
) {
1658 char name
[B_FILE_NAME_LENGTH
];
1659 strlcpy(name
, ref
.name
, sizeof(name
));
1662 BString
suffix(" ");
1663 suffix
<< B_TRANSLATE_COMMENT("link", "filename link"),
1664 FSMakeOriginalName(name
, destDir
, suffix
.String());
1665 undo
.UpdateEntry(entry
, name
);
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
);
1684 destDir
-> GetEntry(&destEntry
);
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
) {
1706 if (*dest
++ == '/') {
1707 lastFolderSrc
= src
;
1708 lastFolderDest
= dest
;
1711 src
= lastFolderSrc
;
1712 dest
= lastFolderDest
;
1715 if (*dest
== '\0' && *src
!= '\0') {
1716 // source is deeper in the same tree than the target
1718 } else if (*dest
!= '\0') {
1719 // target is deeper in the same tree than the source
1722 source
.Prepend("../");
1728 // else source and target are in the same dir
1730 source
.Append(path
.Leaf());
1731 err
= destDir
->CreateSymLink(name
, source
.String(),
1735 // change working dir back to original
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\"."),
1754 if (loc
&& loc
!= (BPoint
*)-1) {
1755 link
.WriteAttr(kAttrPoseInfo
, B_RAW_TYPE
, 0, &poseInfo
,
1759 BNodeInfo
nodeInfo(&link
);
1760 nodeInfo
.SetType(B_LINK_MIMETYPE
);
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
));
1776 bool makeOriginalName
= (moveMode
== kDuplicateSelection
);
1777 if (S_ISDIR(statbuf
.st_mode
)) {
1778 CopyFolder(entry
, destDir
, loopControl
, loc
, makeOriginalName
,
1779 undo
, moveMode
== kMoveSelectionTo
);
1781 CopyFile(entry
, &statbuf
, destDir
, loopControl
, loc
,
1782 makeOriginalName
, undo
);
1783 if (moveMode
== kMoveSelectionTo
)
1787 } catch (status_t error
) {
1788 // no alert, was already taken care of before
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
);
1797 return error
.fError
;
1798 } catch (FailWithAlert error
) {
1799 BString
buffer(error
.fString
);
1800 if (error
.fName
!= NULL
)
1801 buffer
.ReplaceFirst("%name", error
.fName
);
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
);
1810 return error
.fError
;
1818 FSDuplicate(BObjectList
<entry_ref
>* srcList
, BList
* pointList
)
1820 LaunchInNewThread("DupTask", B_NORMAL_PRIORITY
, MoveTask
, srcList
,
1821 (BEntry
*)NULL
, pointList
, kDuplicateSelection
);
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
) {
1841 FSCopyAttributesAndStats(BNode
* srcNode
, BNode
* destNode
, bool copyTimes
)
1843 char* buffer
= new char[1024];
1845 // copy the attributes
1846 srcNode
->RewindAttrs();
1848 while (srcNode
->GetNextAttrName(name
) == B_OK
) {
1850 if (srcNode
->GetAttrInfo(name
, &info
) != B_OK
)
1853 attr_info dest_info
;
1854 if (destNode
->GetAttrInfo(name
, &dest_info
) == B_OK
)
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)
1864 bytes
= srcNode
->ReadAttr(name
, info
.type
, offset
, buffer
,
1870 destNode
->WriteAttr(name
, info
.type
, offset
, 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
);
1885 destNode
->SetModificationTime(srcStat
.st_mtime
);
1886 destNode
->SetCreationTime(srcStat
.st_crtime
);
1895 FSCopyFile(BEntry
* srcFile
, StatStruct
* srcStat
, BDirectory
* destDir
,
1896 CopyLoopControl
* loopControl
, BPoint
* loc
, bool makeOriginalName
)
1899 CopyFile(srcFile
, srcStat
, destDir
, loopControl
, loc
,
1901 } catch (status_t error
) {
1911 MoveEntryToTrash(BEntry
* entry
, BPoint
* loc
, Undo
&undo
)
1913 BDirectory trash_dir
;
1915 status_t result
= entry
->GetRef(&ref
);
1920 result
= entry
->GetNodeRef(&nodeRef
);
1925 result
= entry
->GetStat(&statbuf
);
1926 if (entry
->GetStat(&statbuf
) != B_OK
)
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
);
1938 BVolumeRoster().GetBootVolume(&boot
);
1939 if (volume
== boot
) {
1940 char name
[B_FILE_NAME_LENGTH
];
1941 volume
.GetName(name
);
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
,
1948 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1951 BMessage
message(kUnmountVolume
);
1952 message
.AddInt32("device_id", volume
.Device());
1953 be_app
->PostMessage(&message
);
1958 // get trash directory on same volume as item being moved
1959 result
= FSGetTrashDir(&trash_dir
, nodeRef
.device
);
1963 // check hierarchy before moving
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) "
1972 0, 0, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
1973 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1976 // return no error so we don't get two dialogs
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
);
1988 // get trash directory on same volume as item being moved
1989 result
= FSGetTrashDir(&trash_dir
, nodeRef
.device
);
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
);
2009 poseInfo
.fInvisible
= false;
2010 poseInfo
.fInitedDirectory
= statbuf
.st_ino
;
2011 poseInfo
.fLocation
= *loc
;
2012 src_node
->WriteAttr(kAttrPoseInfo
, B_RAW_TYPE
, 0, &poseInfo
,
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
,
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
);
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()) {
2071 // user selected "Prompt"
2075 // user selected "Skip all"
2079 // user selected "Replace all"
2084 return kNoConflicts
;
2089 FileStatToString(StatStruct
* stat
, char* buffer
, int32 length
)
2092 localtime_r(&stat
->st_mtime
, &timeData
);
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
);
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
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
);
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
);
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
,
2143 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
2149 if (destDir
->FindEntry(name
, &entry
) != B_OK
) {
2150 // no conflict, return
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
)
2163 bool destIsDir
= entry
.IsDirectory();
2164 // be sure not to replace the parent directory of the item being moved
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
);
2178 // ensure that the user isn't trying to replace a file with folder
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 "
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
,
2189 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
2194 if (conflictResolution
== kSkipAll
)
2197 if (conflictResolution
!= kReplaceAll
) {
2198 // prompt user to determine whether to replace or not
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"));
2212 char sourceBuffer
[96], destBuffer
[96];
2213 StatStruct statBuffer
;
2215 if (!sourceEntry
->IsDirectory()
2216 && sourceEntry
->GetStat(&statBuffer
) == B_OK
) {
2217 FileStatToString(&statBuffer
, sourceBuffer
, 96);
2219 sourceBuffer
[0] = '\0';
2221 if (!entry
.IsDirectory() && entry
.GetStat(&statBuffer
) == B_OK
)
2222 FileStatToString(&statBuffer
, destBuffer
, 96);
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)
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()) {
2245 conflictResolution
= kCanceled
;
2248 conflictResolution
= kSkipAll
;
2251 conflictResolution
= kReplace
;
2254 conflictResolution
= kReplaceAll
;
2258 alert
= new BAlert("", replaceMsg
.String(),
2259 B_TRANSLATE("Cancel"), B_TRANSLATE("Replace"));
2260 alert
->SetShortcut(0, B_ESCAPE
);
2261 switch (alert
->Go()) {
2263 conflictResolution
= kCanceled
;
2266 conflictResolution
= kReplace
;
2272 // delete destination item
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
);
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
2299 while (dir
.GetNextEntry(&entry
) == B_OK
) {
2303 if (loopControl
->CheckUserCanceled())
2304 return kTrashCanceled
;
2308 if (entry
.IsDirectory())
2309 status
= FSDeleteFolder(&entry
, loopControl
, updateStatus
, true,
2310 upateFileNameInStatus
);
2312 status
= entry
.Remove();
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
;
2332 dirEntry
->GetRef(&ref
);
2334 if (updateStatus
&& deleteTopDir
)
2335 loopControl
->UpdateStatus(NULL
, ref
, 1);
2338 return dirEntry
->Remove();
2345 FSMakeOriginalName(BString
&string
, const BDirectory
* destDir
,
2348 if (!destDir
->Contains(string
.String()))
2351 FSMakeOriginalName(string
.LockBuffer(B_FILE_NAME_LENGTH
),
2352 const_cast<BDirectory
*>(destDir
), suffix
? suffix
: " copy");
2353 string
.UnlockBuffer();
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];
2365 // is this name already original?
2366 if (!destDir
->Contains(name
))
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
))
2384 // eat up optional spaces
2385 while ((p
> name
) && isspace(*p
))
2388 // now look for the phrase " copy"
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'
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';
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
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
);
2445 FSRecursiveCalcSize(BInfoWindow
* window
, CopyLoopControl
* loopControl
,
2446 BDirectory
* dir
, off_t
* _runningSize
, int32
* _fileCount
, int32
* _dirCount
)
2450 while (dir
->GetNextEntry(&entry
) == B_OK
) {
2451 // be sure window hasn't closed
2452 if (window
&& window
->StopCalc())
2455 if (loopControl
->CheckUserCanceled())
2456 return kUserCanceled
;
2459 status_t status
= entry
.GetStat(&statbuf
);
2463 (*_runningSize
) += statbuf
.st_blocks
* 512;
2465 if (S_ISDIR(statbuf
.st_mode
)) {
2466 BDirectory
subdir(&entry
);
2468 status
= FSRecursiveCalcSize(window
, loopControl
, &subdir
,
2469 _runningSize
, _fileCount
, _dirCount
);
2480 CalcItemsAndSize(CopyLoopControl
* loopControl
,
2481 BObjectList
<entry_ref
>* refList
, ssize_t blockSize
, int32
* totalCount
,
2484 int32 fileCount
= 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.
2494 } else if (blockSize
< 1024) {
2496 if (entry_ref
* ref
= refList
->ItemAt(0)) {
2497 // TODO: This assumes all entries in the list share the same
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)
2509 int32 num_items
= refList
->CountItems();
2510 for (int32 i
= 0; i
< num_items
; i
++) {
2511 entry_ref
* ref
= refList
->ItemAt(i
);
2514 entry
.GetStat(&statbuf
);
2516 if (loopControl
->CheckUserCanceled())
2517 return kUserCanceled
;
2519 if (S_ISDIR(statbuf
.st_mode
)) {
2520 BDirectory
dir(&entry
);
2522 (*totalSize
) += blockSize
;
2523 status_t result
= FSRecursiveCalcSize(NULL
, loopControl
, &dir
,
2524 totalSize
, &fileCount
, &dirCount
);
2529 (*totalSize
) += statbuf
.st_size
+ blockSize
;
2533 *totalCount
+= (fileCount
+ dirCount
);
2539 FSGetTrashDir(BDirectory
* trashDir
, dev_t dev
)
2541 if (trashDir
== NULL
)
2544 BVolume
volume(dev
);
2545 status_t result
= volume
.InitCheck();
2550 result
= find_directory(B_TRASH_DIRECTORY
, &path
, false, &volume
);
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);
2561 result
= trashDir
->SetTo(path
.Path());
2565 // make Trash directory invisible
2567 trashDir
->GetStat(&sbuf
);
2570 poseInfo
.fInvisible
= true;
2571 poseInfo
.fInitedDirectory
= sbuf
.st_ino
;
2572 trashDir
->WriteAttr(kAttrPoseInfo
, B_RAW_TYPE
, 0, &poseInfo
,
2576 // set Trash icons (if they haven't already been set)
2580 if (trashDir
->GetAttrInfo(kAttrLargeIcon
, &attrInfo
) == B_ENTRY_NOT_FOUND
) {
2581 data
= GetTrackerResources()->LoadResource('ICON', R_TrashIcon
, &size
);
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
);
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
);
2596 trashDir
->WriteAttr(kAttrIcon
, B_VECTOR_ICON_TYPE
, 0, data
, size
);
2604 FSGetDeskDir(BDirectory
* deskDir
)
2606 if (deskDir
== NULL
)
2610 status_t result
= find_directory(B_DESKTOP_DIRECTORY
, &path
, true);
2614 result
= deskDir
->SetTo(path
.Path());
2618 // set Desktop icons (if they haven't already been set)
2622 if (deskDir
->GetAttrInfo(kAttrLargeIcon
, &attrInfo
) == B_ENTRY_NOT_FOUND
) {
2623 data
= GetTrackerResources()->LoadResource('ICON', R_DeskIcon
, &size
);
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
);
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
,
2638 deskDir
->WriteAttr(kAttrIcon
, B_VECTOR_ICON_TYPE
, 0, data
, size
);
2646 FSGetBootDeskDir(BDirectory
* deskDir
)
2649 BVolumeRoster().GetBootVolume(&bootVolume
);
2652 status_t result
= find_directory(B_DESKTOP_DIRECTORY
, &path
, true,
2657 return deskDir
->SetTo(path
.Path());
2662 FSIsDirFlavor(const BEntry
* entry
, directory_which directoryType
)
2664 StatStruct dir_stat
;
2665 StatStruct entry_stat
;
2669 if (entry
->GetStat(&entry_stat
) != B_OK
)
2672 if (volume
.SetTo(entry_stat
.st_dev
) != B_OK
)
2675 if (find_directory(directoryType
, &path
, false, &volume
) != B_OK
)
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
;
2686 FSIsPrintersDir(const BEntry
* entry
)
2688 return FSIsDirFlavor(entry
, B_USER_PRINTERS_DIRECTORY
);
2693 FSIsTrashDir(const BEntry
* entry
)
2695 return FSIsDirFlavor(entry
, B_TRASH_DIRECTORY
);
2700 FSIsDeskDir(const BEntry
* entry
)
2703 status_t result
= find_directory(B_DESKTOP_DIRECTORY
, &path
, true);
2707 BEntry
entryToCompare(path
.Path());
2708 return entryToCompare
== *entry
;
2713 FSIsHomeDir(const BEntry
* entry
)
2715 return FSIsDirFlavor(entry
, B_USER_DIRECTORY
);
2720 FSIsRootDir(const BEntry
* entry
)
2728 DirectoryMatchesOrContains(const BEntry
* entry
, directory_which which
)
2731 if (find_directory(which
, &path
, false, NULL
) != B_OK
)
2734 BEntry
dirEntry(path
.Path());
2735 if (dirEntry
.InitCheck() != B_OK
)
2738 if (dirEntry
== *entry
)
2742 BDirectory
dir(&dirEntry
);
2743 return dir
.Contains(entry
);
2748 DirectoryMatchesOrContains(const BEntry
* entry
, const char* additionalPath
,
2749 directory_which which
)
2752 if (find_directory(which
, &path
, false, NULL
) != B_OK
)
2755 path
.Append(additionalPath
);
2756 BEntry
dirEntry(path
.Path());
2757 if (dirEntry
.InitCheck() != B_OK
)
2760 if (dirEntry
== *entry
)
2764 BDirectory
dir(&dirEntry
);
2765 return dir
.Contains(entry
);
2770 DirectoryMatches(const BEntry
* entry
, directory_which which
)
2773 if (find_directory(which
, &path
, false, NULL
) != B_OK
)
2776 BEntry
dirEntry(path
.Path());
2777 if (dirEntry
.InitCheck() != B_OK
)
2780 return dirEntry
== *entry
;
2785 DirectoryMatches(const BEntry
* entry
, const char* additionalPath
,
2786 directory_which which
)
2789 if (find_directory(which
, &path
, false, NULL
) != B_OK
)
2792 path
.Append(additionalPath
);
2793 BEntry
dirEntry(path
.Path());
2794 if (dirEntry
.InitCheck() != B_OK
)
2797 return dirEntry
== *entry
;
2802 FSFindTrackerSettingsDir(BPath
* path
, bool autoCreate
)
2804 status_t result
= find_directory(B_USER_SETTINGS_DIRECTORY
, path
,
2809 path
->Append("Tracker");
2811 return mkdir(path
->Path(), 0777) ? B_OK
: errno
;
2816 FSInTrashDir(const entry_ref
* ref
)
2819 if (entry
.InitCheck() != B_OK
)
2822 BDirectory trashDir
;
2823 if (FSGetTrashDir(&trashDir
, ref
->device
) != B_OK
)
2826 return trashDir
.Contains(&entry
);
2833 if (find_thread("_tracker_empty_trash_") != B_OK
) {
2834 resume_thread(spawn_thread(empty_trash
, "_tracker_empty_trash_",
2835 B_NORMAL_PRIORITY
, NULL
));
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
;
2855 while (volumeRoster
.GetNextVolume(&volume
) == B_OK
) {
2856 if (volume
.IsReadOnly() || !volume
.IsPersistent())
2859 BDirectory trashDirectory
;
2860 if (FSGetTrashDir(&trashDirectory
, volume
.Device()) != B_OK
)
2864 trashDirectory
.GetEntry(&entry
);
2868 srcList
.AddItem(&ref
);
2869 status
= CalcItemsAndSize(&loopControl
, &srcList
, volume
.BlockSize(),
2870 &totalCount
, &totalSize
);
2874 srcList
.MakeEmpty();
2876 // don't count trash directory itself
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())
2888 BDirectory trashDirectory
;
2889 if (FSGetTrashDir(&trashDirectory
, volume
.Device()) != B_OK
)
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
);
2910 _DeleteTask(BObjectList
<entry_ref
>* list
, bool 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
,
2922 alert
->SetShortcut(0, B_ESCAPE
);
2923 alert
->SetShortcut(1, 'm');
2924 alert
->SetShortcut(2, 'd');
2926 switch (alert
->Go()) {
2931 FSMoveToTrash(list
, NULL
, false);
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');
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
,
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
));
2965 loopControl
.UpdateStatus(ref
.name
, ref
, 1, true);
2966 if (entry
.IsDirectory())
2967 status
= FSDeleteFolder(&entry
, &loopControl
, true, true, true);
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
,
2977 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
2988 FSRecursiveCreateFolder(BPath path
)
2990 BEntry
entry(path
.Path());
2991 if (entry
.InitCheck() != B_OK
) {
2993 status_t err
= path
.GetParent(&parentPath
);
2997 err
= FSRecursiveCreateFolder(parentPath
);
3002 entry
.SetTo(path
.Path());
3004 return B_FILE_EXISTS
;
3007 entry
.GetParent(&parent
);
3008 parent
.CreateDirectory(entry
.Name(), NULL
);
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
,
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
));
3033 loopControl
.UpdateStatus(ref
.name
, ref
, 1, true);
3035 if (FSGetOriginalPath(&entry
, &originalPath
) != B_OK
)
3038 BEntry
originalEntry(originalPath
.Path());
3040 err
= originalPath
.GetParent(&parentPath
);
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
)
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
) {
3059 if (node
.InitCheck() == B_OK
)
3060 node
.RemoveAttr(kAttrOriginalPath
);
3065 err
= loopControl
.CheckUserCanceled();
3080 BVolumeRoster roster
;
3083 while (roster
.GetNextVolume(&volume
) == B_OK
) {
3084 if (volume
.IsReadOnly() || !volume
.IsPersistent())
3087 BDirectory trashDir
;
3088 FSGetTrashDir(&trashDir
, volume
.Device());
3094 FSCreateNewFolder(const entry_ref
* ref
)
3097 node
.device
= ref
->device
;
3098 node
.node
= ref
->directory
;
3100 BDirectory
dir(&node
);
3101 status_t result
= dir
.InitCheck();
3105 // ToDo: is that really necessary here?
3106 BString
name(ref
->name
);
3107 FSMakeOriginalName(name
, &dir
, "-");
3110 result
= dir
.CreateDirectory(name
.String(), &newDir
);
3114 BNodeInfo
nodeInfo(&newDir
);
3115 nodeInfo
.SetType(B_DIR_MIMETYPE
);
3122 FSCreateNewFolderIn(const node_ref
* dirNode
, entry_ref
* newRef
,
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
));
3132 while (dir
.Contains(name
)) {
3133 // if base name already exists then add a number
3135 // move this logic ot FSMakeOriginalName
3137 snprintf(name
, sizeof(name
), B_TRANSLATE("New folder%ld"),
3140 snprintf(name
, sizeof(name
), B_TRANSLATE("New folder %ld"),
3146 result
= dir
.CreateDirectory(name
, &newDir
);
3147 if (result
== B_OK
) {
3149 newDir
.GetEntry(&entry
);
3150 entry
.GetRef(newRef
);
3151 entry
.GetNodeRef(newNode
);
3153 BNodeInfo
nodeInfo(&newDir
);
3154 nodeInfo
.SetType(B_DIR_MIMETYPE
);
3157 NewFolderUndo
undo(*newRef
);
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
);
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));
3190 return kReadAttrForeignOK
;
3193 // run the endian swapper
3195 return kReadAttrForeignOK
;
3200 GetAttrInfo(const BNode
* node
, const char* hostAttrName
,
3201 const char* foreignAttrName
, type_code
* type
, size_t* size
)
3205 if (node
->GetAttrInfo(hostAttrName
, &info
) == B_OK
) {
3209 *size
= (size_t)info
.size
;
3211 return kReadAttrNativeOK
;
3214 if (node
->GetAttrInfo(foreignAttrName
, &info
) == B_OK
) {
3218 *size
= (size_t)info
.size
;
3220 return kReadAttrForeignOK
;
3222 return kReadAttrFailed
;
3227 FSGetParentVirtualDirectoryAware(const BEntry
& entry
, entry_ref
& _ref
)
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
,
3244 if ((error
= entry
.GetParent(&parent
)) != B_OK
3245 || (error
= parent
.GetEntry(&parentEntry
)) != B_OK
3246 || (error
= parentEntry
.GetRef(&_ref
)) != B_OK
) {
3255 FSGetParentVirtualDirectoryAware(const BEntry
& entry
, BEntry
& _entry
)
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
,
3265 return _entry
.SetTo(&parentRef
);
3270 return entry
.GetParent(&_entry
);
3275 FSGetParentVirtualDirectoryAware(const BEntry
& entry
, BNode
& _node
)
3278 status_t error
= FSGetParentVirtualDirectoryAware(entry
, ref
);
3280 error
= _node
.SetTo(&ref
);
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
);
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
);
3312 task
->AddRef("appRef", appRef
);
3314 extern BLooper
* gLaunchLooper
;
3315 gLaunchLooper
->PostMessage(task
);
3320 SniffIfGeneric(const entry_ref
* 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
3334 node
.RemoveAttr(kAttrMIMEType
);
3335 update_mime_info(path
.Path(), 0, 1, 1);
3343 SniffIfGeneric(const BMessage
* refs
)
3346 for (int32 index
= 0; ; index
++) {
3347 if (refs
->FindRef("refs", index
, &ref
) != B_OK
)
3349 SniffIfGeneric(&ref
);
3355 _TrackerLaunchAppWithDocuments(const entry_ref
* appRef
, const BMessage
* refs
,
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
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;
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
);
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
);
3403 BAlert
* alert
= new BAlert("", alertString
.String(),
3404 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL
,
3406 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
3412 extern "C" char** environ
;
3416 LoaderErrorDetails(const entry_ref
* app
, BString
&details
)
3419 BEntry
appEntry(app
, true);
3421 status_t result
= appEntry
.GetPath(&path
);
3425 char* argv
[2] = { const_cast<char*>(path
.Path()), 0};
3427 port_id errorPort
= create_port(1, "Tracker loader error");
3429 // count environment variables
3431 while (environ
[envCount
] != NULL
)
3434 char** flatArgs
= NULL
;
3435 size_t flatArgsSize
;
3436 result
= __flatten_process_args((const char**)argv
, 1,
3437 environ
, &envCount
, argv
[0], &flatArgs
, &flatArgsSize
);
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...
3448 // read error message from port and construct details string
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
);
3461 uint8
* buffer
= (uint8
*)malloc(bufferSize
);
3462 if (buffer
== NULL
) {
3463 delete_port(errorPort
);
3467 bufferSize
= read_port_etc(errorPort
, NULL
, buffer
, bufferSize
,
3468 B_RELATIVE_TIMEOUT
, 0);
3469 delete_port(errorPort
);
3471 if (bufferSize
< B_OK
) {
3477 result
= message
.Unflatten((const char*)buffer
);
3483 int32 errorCode
= B_ERROR
;
3484 result
= message
.FindInt32("error", &errorCode
);
3488 const char* detailName
= NULL
;
3489 switch (errorCode
) {
3490 case B_MISSING_LIBRARY
:
3491 detailName
= "missing library";
3494 case B_MISSING_SYMBOL
:
3495 detailName
= "missing symbol";
3499 if (detailName
== NULL
)
3503 for (int32 i
= 0; message
.FindString(detailName
, i
, &detail
) == B_OK
;
3515 _TrackerLaunchDocuments(const entry_ref
*, const BMessage
* refs
,
3521 BMessage
copyOfRefs(*refs
);
3523 entry_ref documentRef
;
3524 if (copyOfRefs
.FindRef("refs", &documentRef
) != B_OK
) {
3525 // nothing to launch, we are done
3529 status_t error
= B_ERROR
;
3531 BMessage
* refsToPass
= NULL
;
3532 BString alertString
;
3533 const char* alternative
= 0;
3535 for (int32 mimesetIt
= 0; ; mimesetIt
++) {
3537 error
= be_roster
->FindApp(&documentRef
, &app
);
3539 if (error
!= B_OK
&& mimesetIt
== 0) {
3540 SniffIfGeneric(©OfRefs
);
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
));
3550 alternative
= B_TRANSLATE_NOCOLLECT(kFindApplicationStr
);
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
3559 if (copyOfRefs
.FindRef("refs", index
, &ref
) != B_OK
)
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
);
3568 PRINT(("leaving %s, app %s \n", ref
.name
, app
.name
));
3573 refsToPass
= CountRefs(©OfRefs
) > 0 ? ©OfRefs
: 0;
3575 error
= be_roster
->Launch(&app
, refsToPass
, &team
);
3576 if (error
== B_ALREADY_RUNNING
)
3577 // app already running, not really an error
3579 if (error
== B_OK
|| mimesetIt
!= 0)
3582 SniffIfGeneric(©OfRefs
);
3586 if (error
!= B_OK
&& alertString
.Length() == 0) {
3587 BString loaderErrorString
;
3588 bool openedDocuments
= true;
3591 // we just double clicked the app itself, do not offer to
3592 // find a handling app
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 "
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
);
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
);
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);
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
));
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
) {
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). "
3663 alertString
.ReplaceFirst("%document", documentRef
.name
);
3664 alertString
.ReplaceFirst("%app", app
.name
);
3665 alertString
.ReplaceFirst("%symbol",
3666 loaderErrorString
.String());
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). "
3681 alertString
.ReplaceFirst("%document", documentRef
.name
);
3682 alertString
.ReplaceFirst("%app", app
.name
);
3683 alertString
.ReplaceFirst("%library",
3684 loaderErrorString
.String());
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
);
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
) {
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
);
3714 BAlert
* alert
= new BAlert("", alertString
.String(),
3715 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL
,
3717 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
3723 // the following three calls don't return any reasonable error codes,
3724 // should fix that, making them void
3727 TrackerLaunch(const entry_ref
* appRef
, const BMessage
* refs
, bool async
,
3731 _TrackerLaunchAppWithDocuments(appRef
, refs
, openWithOK
);
3733 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments
, appRef
, refs
,
3741 TrackerLaunch(const entry_ref
* appRef
, bool async
)
3744 _TrackerLaunchAppWithDocuments(appRef
, NULL
, false);
3746 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments
, appRef
, 0, false);
3752 TrackerLaunch(const BMessage
* refs
, bool async
, bool openWithOK
)
3755 _TrackerLaunchDocuments(NULL
, refs
, openWithOK
);
3757 AsynchLaunchBinder(&_TrackerLaunchDocuments
, NULL
, refs
, openWithOK
);
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
));
3772 // external launch calls; need to be robust, work if Tracker is not running
3775 #if !B_BEOS_VERSION_DANO
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
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
);
3798 ASSERT(!"not yet implemented");
3804 // legacy calls, need for compatibility
3808 FSOpenWithDocuments(const entry_ref
* executable
, BMessage
* documents
)
3810 TrackerLaunch(executable
, documents
, true);
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
);
3824 FSOpenWith(listOfRefs
);
3831 FSLaunchItem(const entry_ref
* appRef
, BMessage
* refs
, int32
, bool async
)
3834 refs
->what
= B_REFS_RECEIVED
;
3836 status_t result
= TrackerLaunch(appRef
, refs
, async
, true);
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
3852 FSGetOriginalPath(BEntry
* entry
, BPath
* result
)
3856 err
= entry
->GetRef(&ref
);
3860 // Only call the routine for entries in the trash
3861 if (!FSInTrashDir(&ref
))
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());
3872 // Iterate the parent directories to find one with
3873 // the original path attribute
3874 BEntry
parent(*entry
);
3875 err
= parent
.InitCheck();
3879 // walk up the directory structure until we find a node
3880 // with original path attribute
3882 // move to the parent of this node
3883 err
= parent
.GetParent(&parent
);
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
);
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());
3903 BPath path
, pathParent
;
3904 err
= parent
.GetPath(&pathParent
);
3908 err
= entry
->GetPath(&path
);
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
3922 WellKnowEntryList::Match(const node_ref
* node
)
3924 const WellKnownEntry
* result
= MatchEntry(node
);
3926 return result
->which
;
3928 return (directory_which
)-1;
3932 const WellKnowEntryList::WellKnownEntry
*
3933 WellKnowEntryList::MatchEntry(const node_ref
* node
)
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
];
3956 WellKnowEntryList::Quit()
3964 WellKnowEntryList::AddOne(directory_which which
, const char* name
)
3967 if (find_directory(which
, &path
, true) != B_OK
)
3970 BEntry
entry(path
.Path(), true);
3972 if (entry
.GetNodeRef(&node
) != B_OK
)
3975 entries
.push_back(WellKnownEntry(&node
, which
, name
));
3980 WellKnowEntryList::AddOne(directory_which which
, directory_which base
,
3981 const char* extra
, const char* name
)
3984 if (find_directory(base
, &path
, true) != B_OK
)
3988 BEntry
entry(path
.Path(), true);
3990 if (entry
.GetNodeRef(&node
) != B_OK
)
3993 entries
.push_back(WellKnownEntry(&node
, which
, name
));
3998 WellKnowEntryList::AddOne(directory_which which
, const char* path
,
4001 BEntry
entry(path
, true);
4003 if (entry
.GetNodeRef(&node
) != B_OK
)
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",
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