Files
haiku-beta6/src/kits/tracker/FSUtils.cpp
T
Stephan Aßmus 080385d599 Fixed logic to detect and warn about changes in the system folder. Merged
the "system" folder and "system folder or it's contents" case.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30057 a95241bf-73f2-0310-859d-f6bbb57e9c96
2009-04-09 09:30:33 +00:00

3544 lines
89 KiB
C++

/*
Open Tracker License
Terms and Conditions
Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice applies to all licensees
and shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Be Incorporated shall not be
used in advertising or otherwise to promote the sale, use or other dealings in
this Software without prior written authorization from Be Incorporated.
Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
of Be Incorporated in the United States and other countries. Other brand product
names are registered trademarks or trademarks of their respective holders.
All rights reserved.
*/
// Tracker file system calls.
// Note - APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup
// -- in other words, you will find a lot of ugly cruft in here
// ToDo:
// Move most of preflight error checks to the Model level and only keep those
// that have to do with size, reading/writing and name collisions.
// Get rid of all the BList based APIs, use BObjectLists.
// Clean up the error handling, push most of the user interaction out of the
// low level FS calls.
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <Alert.h>
#include <Application.h>
#include <Debug.h>
#include <Directory.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Roster.h>
#include <Screen.h>
#include <String.h>
#include <SymLink.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include <fs_attr.h>
#include <fs_info.h>
#include "Attributes.h"
#include "Bitmaps.h"
#include "Commands.h"
#include "FSUndoRedo.h"
#include "FSUtils.h"
#include "InfoWindow.h"
#include "MimeTypes.h"
#include "Model.h"
#include "OverrideAlert.h"
#include "StatusWindow.h"
#include "Thread.h"
#include "Tracker.h"
#include "TrackerSettings.h"
#include "Utilities.h"
enum {
kUserCanceled = B_ERRORS_END + 1,
kCopyCanceled = kUserCanceled,
kTrashCanceled
};
enum ConflictCheckResult {
kCanceled = kUserCanceled,
kPrompt,
kReplace,
kReplaceAll,
kNoConflicts
};
namespace BPrivate {
static status_t FSDeleteFolder(BEntry *, CopyLoopControl *, bool updateStatus,
bool deleteTopDir = true, bool upateFileNameInStatus = false);
static status_t MoveEntryToTrash(BEntry *, BPoint *, Undo &undo);
static void LowLevelCopy(BEntry *, StatStruct *, BDirectory *, char *destName,
CopyLoopControl *, BPoint *);
status_t DuplicateTask(BObjectList<entry_ref> *srcList);
static status_t MoveTask(BObjectList<entry_ref> *, BEntry *, BList *, uint32);
static status_t _DeleteTask(BObjectList<entry_ref> *, bool);
static status_t _RestoreTask(BObjectList<entry_ref> *);
status_t CalcItemsAndSize(BObjectList<entry_ref> *refList, int32 *totalCount, off_t *totalSize);
status_t MoveItem(BEntry *entry, BDirectory *destDir, BPoint *loc,
uint32 moveMode, const char *newName, Undo &undo);
ConflictCheckResult PreFlightNameCheck(BObjectList<entry_ref> *srcList, const BDirectory *destDir,
int32 *collisionCount, uint32 moveMode);
status_t CheckName(uint32 moveMode, const BEntry *srcEntry, const BDirectory *destDir,
bool multipleCollisions, ConflictCheckResult &);
void CopyAttributes(CopyLoopControl *control, BNode *srcNode, BNode* destNode, void *buffer,
size_t bufsize);
void CopyPoseLocation(BNode *src, BNode *dest);
bool DirectoryMatchesOrContains(const BEntry *, directory_which);
bool DirectoryMatchesOrContains(const BEntry *, const char *additionalPath, directory_which);
bool DirectoryMatches(const BEntry *, directory_which);
bool DirectoryMatches(const BEntry *, const char *additionalPath, directory_which);
status_t empty_trash(void *);
#ifdef __HAIKU__
#define OS_NAME "Haiku"
#else
#define OS_NAME "BeOS"
#endif
const char *kDeleteConfirmationStr = "Are you sure you want to delete the selected "
"item(s)? This operation cannot be reverted.";
const char *kReplaceStr = "You are trying to replace the item:\n"
"\t%s%s\n"
"with:\n"
"\t%s%s\n\n"
"Would you like to replace it with the one you are %s?";
const char *kDirectoryReplaceStr = "An item named \"%s\" already exists in this folder, and may contain\n"
"items with the same names. Would you like to replace them with those contained in the folder you are %s?";
const char *kSymLinkReplaceStr = "An item named \"%s\" already exists in this folder. "
"Would you like to replace it with the symbolic link you are creating?";
const char *kNoFreeSpace = "Sorry, there is not enough free space on the destination "
"volume to copy the selection.";
const char *kFileErrorString = "Error copying file \"%s\":\n\t%s\n\nWould you like to continue?";
const char *kFolderErrorString = "Error copying folder \"%s\":\n\t%s\n\nWould you like to continue?";
const char *kFileDeleteErrorString = "There was an error deleting \"%s\":\n\t%s";
const char *kReplaceManyStr = "Some items already exist in this folder with "
"the same names as the items you are %s.\n \nWould you like to replace them "
"with the ones you are %s or be prompted for each one?";
const char *kFindAlternativeStr = "Would you like to find some other suitable application?";
const char *kFindApplicationStr = "Would you like to find a suitable application to "
"open the file?";
// Skip these attributes when copying in Tracker
const char *kSkipAttributes[] = {
kAttrPoseInfo,
NULL
};
CopyLoopControl::~CopyLoopControl()
{
}
bool
TrackerCopyLoopControl::FileError(const char *message, const char *name,
status_t error, bool allowContinue)
{
char buffer[512];
sprintf(buffer, message, name, strerror(error));
if (allowContinue) {
BAlert *alert = new BAlert("", buffer, "Cancel", "OK", 0,
B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetShortcut(0, B_ESCAPE);
return alert->Go() != 0;
}
BAlert *alert = new BAlert("", buffer, "Cancel", 0, 0,
B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
return false;
}
void
TrackerCopyLoopControl::UpdateStatus(const char *name, entry_ref, int32 count,
bool optional)
{
if (gStatusWindow && gStatusWindow->HasStatus(fThread))
gStatusWindow->UpdateStatus(fThread, const_cast<char *>(name),
count, optional);
}
bool
TrackerCopyLoopControl::CheckUserCanceled()
{
return gStatusWindow && gStatusWindow->CheckCanceledOrPaused(fThread);
}
TrackerCopyLoopControl::OverwriteMode
TrackerCopyLoopControl::OverwriteOnConflict(const BEntry *, const char *,
const BDirectory *, bool, bool)
{
return kReplace;
}
bool
TrackerCopyLoopControl::SkipEntry(const BEntry *, bool)
{
// tracker makes no exceptions
return false;
}
bool
TrackerCopyLoopControl::SkipAttribute(const char *attributeName)
{
for (const char **skipAttribute = kSkipAttributes; *skipAttribute;
skipAttribute++)
if (strcmp(*skipAttribute, attributeName) == 0)
return true;
return false;
}
void
CopyLoopControl::ChecksumChunk(const char *, size_t)
{
}
bool
CopyLoopControl::ChecksumFile(const entry_ref *)
{
return true;
}
bool
CopyLoopControl::SkipAttribute(const char*)
{
return false;
}
bool
CopyLoopControl::PreserveAttribute(const char*)
{
return false;
}
static BNode *
GetWritableNode(BEntry *entry, StatStruct *statBuf = 0)
{
// utility call that works around the problem with BNodes not being
// universally writeable
// BNodes created on files will fail to WriteAttr because they do not
// have the right r/w permissions
StatStruct localStatbuf;
if (!statBuf) {
statBuf = &localStatbuf;
if (entry->GetStat(statBuf) != B_OK)
return 0;
}
if (S_ISREG(statBuf->st_mode))
return new BFile(entry, O_RDWR);
return new BNode(entry);
}
status_t
FSSetPoseLocation(ino_t destDirInode, BNode *destNode, BPoint point)
{
PoseInfo poseInfo;
poseInfo.fInvisible = false;
poseInfo.fInitedDirectory = destDirInode;
poseInfo.fLocation = point;
status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
&poseInfo, sizeof(poseInfo));
if (result == sizeof(poseInfo))
return B_OK;
return result;
}
status_t
FSSetPoseLocation(BEntry *entry, BPoint point)
{
BNode node(entry);
status_t result = node.InitCheck();
if (result != B_OK)
return result;
BDirectory parent;
result = entry->GetParent(&parent);
if (result != B_OK)
return result;
node_ref destNodeRef;
result = parent.GetNodeRef(&destNodeRef);
if (result != B_OK)
return result;
return FSSetPoseLocation(destNodeRef.node, &node, point);
}
bool
FSGetPoseLocation(const BNode *node, BPoint *point)
{
PoseInfo poseInfo;
if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign,
B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap)
== kReadAttrFailed)
return false;
if (poseInfo.fInitedDirectory == -1LL)
return false;
*point = poseInfo.fLocation;
return true;
}
static void
SetUpPoseLocation(ino_t sourceParentIno, ino_t destParentIno,
const BNode *sourceNode, BNode *destNode, BPoint *loc)
{
BPoint point;
if (!loc
// we don't have a position yet
&& sourceParentIno != destParentIno
// we aren't copying into the same directory
&& FSGetPoseLocation(sourceNode, &point))
// the original has a valid inited location
loc = &point;
// copy the originals location
if (loc && loc != (BPoint *)-1) {
// loc of -1 is used when copying/moving into a window in list mode
// where copying positions would not work
// ToSo:
// should push all this logic to upper levels
FSSetPoseLocation(destParentIno, destNode, *loc);
}
}
void
FSMoveToFolder(BObjectList<entry_ref> *srcList, BEntry *destEntry,
uint32 moveMode, BList *pointList)
{
if (srcList->IsEmpty()) {
delete srcList;
delete pointList;
delete destEntry;
return;
}
LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, destEntry,
pointList, moveMode);
}
void
FSDelete(entry_ref *ref, bool async, bool confirm)
{
BObjectList<entry_ref> *list = new BObjectList<entry_ref>(1, true);
list->AddItem(ref);
FSDeleteRefList(list, async, confirm);
}
void
FSDeleteRefList(BObjectList<entry_ref> *list, bool async, bool confirm)
{
if (async)
LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list, confirm);
else
_DeleteTask(list, confirm);
}
void
FSRestoreRefList(BObjectList<entry_ref> *list, bool async)
{
if (async)
LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask, list);
else
_RestoreTask(list);
}
void
FSMoveToTrash(BObjectList<entry_ref> *srcList, BList *pointList, bool async)
{
if (srcList->IsEmpty()) {
delete srcList;
delete pointList;
return;
}
if (async)
LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList,
(BEntry *)0, pointList, kMoveSelectionTo);
else
MoveTask(srcList, 0, pointList, kMoveSelectionTo);
}
static bool
IsDisksWindowIcon(BEntry *entry)
{
BPath path;
if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK)
return false;
return strcmp(path.Path(), "/") == 0;
}
enum {
kNotConfirmed,
kConfirmedHomeMove,
kConfirmedAll
};
bool
ConfirmChangeIfWellKnownDirectory(const BEntry *entry, const char *action,
bool dontAsk, int32 *confirmedAlready)
{
// Don't let the user casually move/change important files/folders
//
// This is a cheap replacement for having a real UID support turned
// on and not running as root all the time
if (confirmedAlready && *confirmedAlready == kConfirmedAll)
return true;
if (!DirectoryMatchesOrContains(entry, B_SYSTEM_DIRECTORY)
&& !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY))
// quick way out
return true;
const char *warning = NULL;
bool requireOverride = true;
if (DirectoryMatchesOrContains(entry, B_BEOS_DIRECTORY)) {
warning = "If you %s the system folder or its contents, you "
"won't be able to boot " OS_NAME "! Are you sure you want to do this? "
"To %s the system folder or its contents anyway, hold down "
"the Shift key and click \"Do it\".";
} else if (DirectoryMatches(entry, B_USER_DIRECTORY)) {
warning = "If you %s the home folder, " OS_NAME " may not "
"behave properly! Are you sure you want to do this? "
"To %s the home anyway, click \"Do it\".";
requireOverride = false;
} else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY)
|| DirectoryMatchesOrContains(entry, B_COMMON_SETTINGS_DIRECTORY)) {
if (DirectoryMatchesOrContains(entry, "beos_mime", B_USER_SETTINGS_DIRECTORY)
|| DirectoryMatchesOrContains(entry, "beos_mime", B_COMMON_SETTINGS_DIRECTORY)) {
warning = "If you %s the mime settings, " OS_NAME " may not "
"behave properly! Are you sure you want to do this? "
"To %s the mime settings anyway, click \"Do it\".";
requireOverride = false;
} else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) {
warning = "If you %s the config folder, " OS_NAME " may not "
"behave properly! Are you sure you want to do this? "
"To %s the config folder anyway, click \"Do it\".";
requireOverride = false;
} else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY)
|| DirectoryMatches(entry, B_COMMON_SETTINGS_DIRECTORY)) {
warning = "If you %s the settings folder, " OS_NAME " may not "
"behave properly! Are you sure you want to do this? "
"To %s the settings folder anyway, click \"Do it\".";
requireOverride = false;
}
}
if (!warning)
return true;
if (dontAsk)
return false;
if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove
&& !requireOverride)
// we already warned about moving home this time around
return true;
char buffer[256];
sprintf(buffer, warning, action, action);
if ((new OverrideAlert("", buffer, "Do it", (requireOverride ? B_SHIFT_KEY : 0),
"Cancel", 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) {
if (confirmedAlready)
*confirmedAlready = kNotConfirmed;
return false;
}
if (confirmedAlready) {
if (!requireOverride)
*confirmedAlready = kConfirmedHomeMove;
else
*confirmedAlready = kConfirmedAll;
}
return true;
}
static status_t
InitCopy(uint32 moveMode, BObjectList<entry_ref> *srcList, thread_id thread,
BVolume *dstVol, BDirectory *destDir, entry_ref *destRef,
bool preflightNameCheck, bool needSizeCalculation, int32 *collisionCount, ConflictCheckResult *preflightResult)
{
if (dstVol->IsReadOnly()) {
if (gStatusWindow)
gStatusWindow->RemoveStatusItem(thread);
BAlert *alert = new BAlert("",
"You can't move or copy items to read-only volumes.",
"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
return B_ERROR;
}
int32 numItems = srcList->CountItems();
int32 askOnceOnly = kNotConfirmed;
for (int32 index = 0; index < numItems; index++) {
// we could check for this while iterating through items in each of the copy
// loops, except it takes forever to call CalcItemsAndSize
BEntry entry((entry_ref *)srcList->ItemAt(index));
if (IsDisksWindowIcon(&entry)) {
const char *errorStr;
if (moveMode == kCreateLink)
errorStr = "You cannot create a link to the root directory.";
else
errorStr = "You cannot copy or move the root directory.";
BAlert *alert = new BAlert("", errorStr, "Cancel", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
return B_ERROR;
}
if (moveMode == kMoveSelectionTo
&& !ConfirmChangeIfWellKnownDirectory(&entry, "move", false, &askOnceOnly))
return B_ERROR;
}
if (preflightNameCheck) {
ASSERT(collisionCount);
ASSERT(preflightResult);
*preflightResult = kPrompt;
*collisionCount = 0;
*preflightResult = PreFlightNameCheck(srcList, destDir, collisionCount, moveMode);
if (*preflightResult == kCanceled) // user canceled
return B_ERROR;
}
// set up the status display
switch (moveMode) {
case kCopySelectionTo:
case kDuplicateSelection:
case kMoveSelectionTo:
{
if (gStatusWindow)
gStatusWindow->CreateStatusItem(thread,
moveMode == kMoveSelectionTo ? kMoveState : kCopyState);
int32 totalItems = 0;
off_t totalSize = 0;
if (needSizeCalculation) {
if (CalcItemsAndSize(srcList, &totalItems, &totalSize) != B_OK)
return B_ERROR;
// check for free space before starting copy
if ((totalSize + (4 * kKBSize)) >= dstVol->FreeBytes()) {
BAlert *alert = new BAlert("", kNoFreeSpace, "Cancel",
0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
return B_ERROR;
}
}
if (gStatusWindow)
gStatusWindow->InitStatusItem(thread, totalItems, totalSize,
destRef);
break;
}
case kCreateLink:
if (numItems > 10) {
// this will be fast, only put up status if lots of items
// moved, links created
if (gStatusWindow) {
gStatusWindow->CreateStatusItem(thread, kCreateLinkState);
gStatusWindow->InitStatusItem(thread, numItems, numItems,
destRef);
}
}
break;
}
return B_OK;
}
// ToDo:
// get rid of this cruft
bool
delete_ref(void *ref)
{
delete (entry_ref*)ref;
return false;
}
bool
delete_point(void *point)
{
delete (BPoint*)point;
return false;
}
static status_t
MoveTask(BObjectList<entry_ref> *srcList, BEntry *destEntry, BList *pointList, uint32 moveMode)
{
ASSERT(!srcList->IsEmpty());
// extract information from src, dest models
// ## note that we're assuming all items come from the same volume
// ## by looking only at FirstItem here which is not a good idea
dev_t srcVolumeDevice = srcList->FirstItem()->device;
dev_t destVolumeDevice = srcVolumeDevice;
StatStruct deststat;
BVolume volume (srcVolumeDevice);
entry_ref destRef;
const entry_ref *destRefToCheck = NULL;
bool destIsTrash = false;
BDirectory destDir;
BDirectory *destDirToCheck = NULL;
bool needPreflightNameCheck = false;
bool sourceIsReadOnly = volume.IsReadOnly();
volume.Unset();
bool fromUndo = FSIsUndoMoveMode(moveMode);
moveMode = FSMoveMode(moveMode);
// if we're not passed a destEntry then we are supposed to move to trash
if (destEntry) {
destEntry->GetRef(&destRef);
destRefToCheck = &destRef;
destDir.SetTo(destEntry);
destDir.GetStat(&deststat);
destDirToCheck = &destDir;
destVolumeDevice = deststat.st_dev;
destIsTrash = FSIsTrashDir(destEntry);
volume.SetTo(destVolumeDevice);
needPreflightNameCheck = true;
} else if (moveMode == kDuplicateSelection)
volume.SetTo(srcVolumeDevice);
else {
// move is to trash
destIsTrash = true;
FSGetTrashDir(&destDir, srcVolumeDevice);
volume.SetTo(srcVolumeDevice);
BEntry entry;
destDir.GetEntry(&entry);
destDirToCheck = &destDir;
entry.GetRef(&destRef);
destRefToCheck = &destRef;
}
// change the move mode if needed
if (moveMode == kCopySelectionTo && destIsTrash)
// cannot copy to trash
moveMode = kMoveSelectionTo;
if (moveMode == kMoveSelectionTo && sourceIsReadOnly)
moveMode = kCopySelectionTo;
bool needSizeCalculation = true;
if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice) || destIsTrash)
needSizeCalculation = false;
// we need the undo object later on, so we create it no matter
// if we really need it or not (it's very lightweight)
MoveCopyUndo undo(srcList, destDir, pointList, moveMode);
if (fromUndo)
undo.Remove();
thread_id thread = find_thread(NULL);
ConflictCheckResult conflictCheckResult = kPrompt;
int32 collisionCount = 0;
status_t result = InitCopy(moveMode, srcList, thread, &volume, destDirToCheck,
&destRef, needPreflightNameCheck, needSizeCalculation, &collisionCount, &conflictCheckResult);
int32 count = srcList->CountItems();
if (result == B_OK) {
for (int32 i = 0; i < count; i++) {
BPoint *loc = (BPoint *)-1;
// a loc of -1 forces autoplacement, rather than copying the
// position of the original node
// ToDo:
// clean this mess up
entry_ref *srcRef = srcList->ItemAt(i);
if (moveMode == kDuplicateSelection) {
BEntry entry(srcRef);
entry.GetParent(&destDir);
destDir.GetStat(&deststat);
volume.SetTo(srcRef->device);
}
// handle case where item is dropped into folder it already lives in
// which could happen if dragging from a query window
if (moveMode != kCreateLink
&& moveMode != kCreateRelativeLink
&& moveMode != kDuplicateSelection
&& !destIsTrash
&& (srcRef->device == destRef.device
&& srcRef->directory == deststat.st_ino))
continue;
if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(thread))
break;
BEntry sourceEntry(srcRef);
if (sourceEntry.InitCheck() != B_OK) {
BString error;
error << "Error moving \"" << srcRef->name << "\".";
BAlert *alert = new BAlert("", error.String(), "Cancel", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
break;
}
// are we moving item to trash?
if (destIsTrash) {
if (pointList)
loc = (BPoint *)pointList->ItemAt(i);
result = MoveEntryToTrash(&sourceEntry, loc, undo);
if (result != B_OK) {
BString error;
error << "Error moving \"" << srcRef->name << "\" to Trash. ("
<< strerror(result) << ")";
BAlert *alert = new BAlert("", error.String(), "Cancel",
0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
break;
}
continue;
}
// resolve name collisions and hierarchy problems
if (CheckName(moveMode, &sourceEntry, &destDir, collisionCount > 1,
conflictCheckResult) != B_OK) {
// we will skip the current item, because we got a conflict
// and were asked to or because there was some conflict
// update the status because item got skipped and the status
// will not get updated by the move call
if (gStatusWindow && gStatusWindow->HasStatus(thread))
gStatusWindow->UpdateStatus(thread, srcRef->name, 1);
continue;
}
// get location to place this item
if (pointList && moveMode != kCopySelectionTo) {
loc = (BPoint *)pointList->ItemAt(i);
BNode *src_node = GetWritableNode(&sourceEntry);
if (src_node && src_node->InitCheck() == B_OK) {
PoseInfo poseInfo;
poseInfo.fInvisible = false;
poseInfo.fInitedDirectory = deststat.st_ino;
poseInfo.fLocation = *loc;
src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
&poseInfo, sizeof(poseInfo));
}
delete src_node;
}
if (pointList)
loc = (BPoint*)pointList->ItemAt(i);
result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL, undo);
if (result != B_OK)
break;
}
}
// duplicates of srcList, destFolder were created - dispose them
delete srcList;
delete destEntry;
// delete file location list and all Points within
if (pointList) {
pointList->DoForEach(delete_point);
delete pointList;
}
if (gStatusWindow)
gStatusWindow->RemoveStatusItem(thread);
return B_OK;
}
class FailWithAlert {
public:
static void FailOnError(status_t error, const char *string, const char *name = NULL)
{
if (error != B_OK)
throw FailWithAlert(error, string, name);
}
FailWithAlert(status_t error, const char *string, const char *name)
: fString(string),
fName(name),
fError(error)
{
}
const char *fString;
const char *fName;
status_t fError;
};
class MoveError {
public:
static void FailOnError(status_t error)
{
if (error != B_OK)
throw MoveError(error);
}
MoveError(status_t error)
: fError(error)
{ }
status_t fError;
};
void
CopyFile(BEntry *srcFile, StatStruct *srcStat, BDirectory *destDir,
CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName, Undo &undo)
{
if (loopControl->SkipEntry(srcFile, true))
return;
node_ref node;
destDir->GetNodeRef(&node);
BVolume volume(node.device);
// check for free space first
if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) {
loopControl->FileError(kNoFreeSpace, "", B_DEVICE_FULL, false);
throw (status_t)B_DEVICE_FULL;
}
char destName[B_FILE_NAME_LENGTH];
srcFile->GetName(destName);
entry_ref ref;
srcFile->GetRef(&ref);
loopControl->UpdateStatus(destName, ref, 1024, true);
if (makeOriginalName) {
FSMakeOriginalName(destName, destDir, " copy");
undo.UpdateEntry(srcFile, destName);
}
BEntry conflictingEntry;
if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) {
switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir,
false, false)) {
case TrackerCopyLoopControl::kSkip:
// we are about to ignore this entire directory
return;
case TrackerCopyLoopControl::kReplace:
if (!conflictingEntry.IsDirectory()) {
ThrowOnError(conflictingEntry.Remove());
break;
}
// fall through if not a directory
case TrackerCopyLoopControl::kMerge:
// This flag implies that the attributes should be kept
// on the file. Just ignore it.
break;
}
}
try {
LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc);
} catch (status_t err) {
if (err == kCopyCanceled)
throw (status_t)err;
if (err != B_OK) {
if (!loopControl->FileError(kFileErrorString, destName, err, true))
throw (status_t)err;
else
// user selected continue in spite of error, update status bar
loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size);
}
}
}
#ifdef _SILENTLY_CORRECT_FILE_NAMES
static bool
CreateFileSystemCompatibleName(const BDirectory *destDir, char *destName)
{
// Is it a FAT32 file system? (this is the only one we currently now about)
BEntry target;
destDir->GetEntry(&target);
entry_ref targetRef;
fs_info info;
if (target.GetRef(&targetRef) == B_OK
&& fs_stat_dev(targetRef.device, &info) == B_OK
&& !strcmp(info.fsh_name, "dos")) {
bool wasInvalid = false;
// it's a FAT32 file system, now check the name
int32 length = strlen(destName) - 1;
while (destName[length] == '.') {
// invalid name, just cut off the dot at the end
destName[length--] = '\0';
wasInvalid = true;
}
char *invalid = destName;
while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) {
invalid[0] = '_';
wasInvalid = true;
}
return wasInvalid;
}
return false;
}
#endif
static void
LowLevelCopy(BEntry *srcEntry, StatStruct *srcStat, BDirectory *destDir,
char *destName, CopyLoopControl *loopControl, BPoint *loc)
{
entry_ref ref;
ThrowOnError(srcEntry->GetRef(&ref));
if (S_ISLNK(srcStat->st_mode)) {
// handle symbolic links
BSymLink srcLink;
BSymLink newLink;
char linkpath[MAXPATHLEN];
ThrowOnError(srcLink.SetTo(srcEntry));
ThrowIfNotSize(srcLink.ReadLink(linkpath, MAXPATHLEN-1));
ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink));
node_ref destNodeRef;
destDir->GetNodeRef(&destNodeRef);
// copy or write new pose location as a first thing
SetUpPoseLocation(ref.directory, destNodeRef.node, &srcLink,
&newLink, loc);
BNodeInfo nodeInfo(&newLink);
ThrowOnError(nodeInfo.SetType(B_LINK_MIMETYPE));
newLink.SetPermissions(srcStat->st_mode);
newLink.SetOwner(srcStat->st_uid);
newLink.SetGroup(srcStat->st_gid);
newLink.SetModificationTime(srcStat->st_mtime);
newLink.SetCreationTime(srcStat->st_crtime);
return;
}
BFile srcFile(srcEntry, O_RDONLY);
ThrowOnInitCheckError(&srcFile);
const size_t kMinBufferSize = 1024 * 128;
const size_t kMaxBufferSize = 1024 * 1024;
size_t bufsize = kMinBufferSize;
if (bufsize < srcStat->st_size) {
// File bigger than the buffer size: determine an optimal buffer size
system_info sinfo;
get_system_info(&sinfo);
size_t freesize = static_cast<size_t>((sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE);
bufsize = freesize / 4; // take 1/4 of RAM max
bufsize -= bufsize % (16 * 1024); // Round to 16 KB boundaries
if (bufsize < kMinBufferSize) // at least kMinBufferSize
bufsize = kMinBufferSize;
else if (bufsize > kMaxBufferSize) // no more than kMaxBufferSize
bufsize = kMaxBufferSize;
}
BFile destFile(destDir, destName, O_RDWR | O_CREAT);
#ifdef _SILENTLY_CORRECT_FILE_NAMES
if ((destFile.InitCheck() == B_BAD_VALUE || destFile.InitCheck() == B_NOT_ALLOWED)
&& CreateFileSystemCompatibleName(destDir, destName))
destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE);
#endif
ThrowOnInitCheckError(&destFile);
node_ref destNodeRef;
destDir->GetNodeRef(&destNodeRef);
// copy or write new pose location as a first thing
SetUpPoseLocation(ref.directory, destNodeRef.node, &srcFile,
&destFile, loc);
char *buffer = new char[bufsize];
try {
// copy data portion of file
while (true) {
if (loopControl->CheckUserCanceled()) {
// if copy was canceled, remove partial destination file
destFile.Unset();
BEntry destEntry;
if (destDir->FindEntry(destName, &destEntry) == B_OK)
destEntry.Remove();
throw (status_t)kCopyCanceled;
}
ASSERT(buffer);
ssize_t bytes = srcFile.Read(buffer, bufsize);
if (bytes > 0) {
ssize_t updateBytes = 0;
if (bytes > 32 * 1024) {
// when copying large chunks, update after read and after write
// to get better update granularity
updateBytes = bytes / 2;
loopControl->UpdateStatus(NULL, ref, updateBytes, true);
}
loopControl->ChecksumChunk(buffer, (size_t)bytes);
ssize_t result = destFile.Write(buffer, (size_t)bytes);
if (result != bytes)
throw (status_t)B_ERROR;
loopControl->UpdateStatus(NULL, ref, bytes - updateBytes, true);
} else if (bytes < 0)
// read error
throw (status_t)bytes;
else
// we are done
break;
}
CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize);
} catch (...) {
delete [] buffer;
throw;
}
destFile.SetPermissions(srcStat->st_mode);
destFile.SetOwner(srcStat->st_uid);
destFile.SetGroup(srcStat->st_gid);
destFile.SetModificationTime(srcStat->st_mtime);
destFile.SetCreationTime(srcStat->st_crtime);
delete [] buffer;
if (!loopControl->ChecksumFile(&ref)) {
// File no good. Remove and quit.
destFile.Unset();
BEntry destEntry;
if (destDir->FindEntry(destName, &destEntry) == B_OK)
destEntry.Remove();
throw (status_t)kUserCanceled;
}
}
void
CopyAttributes(CopyLoopControl *control, BNode *srcNode, BNode *destNode, void *buffer,
size_t bufsize)
{
// ToDo:
// Add error checking
// prior to coyping attributes, make sure indices are installed
// When calling CopyAttributes on files, have to make sure destNode
// is a BFile opened R/W
srcNode->RewindAttrs();
char name[256];
while (srcNode->GetNextAttrName(name) == B_OK) {
// Check to see if this attribute should be skipped.
if (control->SkipAttribute(name))
continue;
attr_info info;
if (srcNode->GetAttrInfo(name, &info) != B_OK)
continue;
// Check to see if this attribute should be overwritten when it
// already exists.
if (control->PreserveAttribute(name)) {
attr_info dest_info;
if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
continue;
}
// Special case for a size 0 attribute. It wouldn't be written at all
// otherwise.
if (info.size == 0)
destNode->WriteAttr(name, info.type, 0, buffer, 0);
ssize_t bytes;
ssize_t numToRead = (ssize_t)info.size;
for (off_t offset = 0; numToRead > 0; offset += bytes) {
size_t chunkSize = (size_t)numToRead;
if (chunkSize > bufsize)
chunkSize = bufsize;
bytes = srcNode->ReadAttr(name, info.type, offset,
buffer, chunkSize);
if (bytes <= 0)
break;
destNode->WriteAttr(name, info.type, offset, buffer, (size_t)bytes);
numToRead -= bytes;
}
}
}
static void
CopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl,
BPoint *loc, bool makeOriginalName, Undo &undo, bool removeSource = false)
{
BDirectory newDir;
BEntry entry;
status_t err = B_OK;
bool createDirectory = true;
BEntry existingEntry;
if (loopControl->SkipEntry(srcEntry, false))
return;
entry_ref ref;
srcEntry->GetRef(&ref);
char destName[B_FILE_NAME_LENGTH];
strcpy(destName, ref.name);
loopControl->UpdateStatus(ref.name, ref, 1024, true);
if (makeOriginalName) {
FSMakeOriginalName(destName, destDir, " copy");
undo.UpdateEntry(srcEntry, destName);
}
if (destDir->FindEntry(destName, &existingEntry) == B_OK) {
// some entry with a conflicting name is already present in destDir
// decide what to do about it
bool isDirectory = existingEntry.IsDirectory();
switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir,
true, isDirectory)) {
case TrackerCopyLoopControl::kSkip:
// we are about to ignore this entire directory
return;
case TrackerCopyLoopControl::kReplace:
if (!isDirectory) {
// conflicting with a file or symbolic link, remove entry
ThrowOnError(existingEntry.Remove());
break;
}
// fall through if directory, do not replace.
case TrackerCopyLoopControl::kMerge:
ASSERT(isDirectory);
// do not create a new directory, use the current one
newDir.SetTo(&existingEntry);
createDirectory = false;
break;
}
}
// loop through everything in src folder and copy it to new folder
BDirectory srcDir(srcEntry);
srcDir.Rewind();
// create a new folder inside of destination folder
if (createDirectory) {
err = destDir->CreateDirectory(destName, &newDir);
#ifdef _SILENTLY_CORRECT_FILE_NAMES
if (err == B_BAD_VALUE) {
// check if it's an invalid name on a FAT32 file system
if (CreateFileSystemCompatibleName(destDir, destName))
err = destDir->CreateDirectory(destName, &newDir);
}
#endif
if (err != B_OK) {
if (!loopControl->FileError(kFolderErrorString, destName, err, true))
throw err;
// will allow rest of copy to continue
return;
}
}
char *buffer;
if (createDirectory && err == B_OK && (buffer = (char*)malloc(32768)) != 0) {
CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768);
// don't copy original pose location if new location passed
free(buffer);
}
StatStruct statbuf;
srcDir.GetStat(&statbuf);
dev_t sourceDeviceID = statbuf.st_dev;
// copy or write new pose location
node_ref destNodeRef;
destDir->GetNodeRef(&destNodeRef);
SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir,
&newDir, loc);
while (srcDir.GetNextEntry(&entry) == B_OK) {
if (loopControl->CheckUserCanceled())
throw (status_t)kUserCanceled;
entry.GetStat(&statbuf);
if (S_ISDIR(statbuf.st_mode)) {
// entry is a mount point, do not copy it
if (statbuf.st_dev != sourceDeviceID) {
PRINT(("Avoiding mount point %d, %d \n", statbuf.st_dev, sourceDeviceID));
continue;
}
CopyFolder(&entry, &newDir, loopControl, 0, false, undo, removeSource);
if (removeSource)
FSDeleteFolder(&entry, loopControl, true, true, false);
} else {
CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo);
if (removeSource)
entry.Remove();
}
}
if (removeSource)
srcEntry->Remove();
else
srcEntry->Unset();
}
status_t
RecursiveMove(BEntry *entry, BDirectory *destDir)
{
TrackerCopyLoopControl loopControl(find_thread(NULL));
char name[B_FILE_NAME_LENGTH];
if (entry->GetName(name) == B_OK) {
if (destDir->Contains(name)) {
BPath path (destDir, name);
BDirectory subDir (path.Path());
entry_ref ref;
entry->GetRef(&ref);
BDirectory source(&ref);
if (source.InitCheck() == B_OK) {
source.Rewind();
BEntry current;
while (source.GetNextEntry(&current) == B_OK) {
if (current.IsDirectory()) {
RecursiveMove(&current, &subDir);
current.Remove();
} else {
current.GetName(name);
if (loopControl.OverwriteOnConflict(&current, name, &subDir, true, false) != TrackerCopyLoopControl::kSkip)
MoveError::FailOnError(current.MoveTo(&subDir, NULL, true));
}
}
}
entry->Remove();
} else {
MoveError::FailOnError(entry->MoveTo(destDir));
}
}
return B_OK;
}
status_t
MoveItem(BEntry *entry, BDirectory *destDir, BPoint *loc, uint32 moveMode,
const char *newName, Undo &undo)
{
entry_ref ref;
try {
node_ref destNode;
StatStruct statbuf;
MoveError::FailOnError(entry->GetStat(&statbuf));
MoveError::FailOnError(entry->GetRef(&ref));
MoveError::FailOnError(destDir->GetNodeRef(&destNode));
if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
PoseInfo poseInfo;
char name[B_FILE_NAME_LENGTH];
strcpy(name, ref.name);
BSymLink link;
FSMakeOriginalName(name, destDir, " link");
undo.UpdateEntry(entry, name);
BPath path;
entry->GetPath(&path);
if (loc && loc != (BPoint *)-1) {
poseInfo.fInvisible = false;
poseInfo.fInitedDirectory = destNode.node;
poseInfo.fLocation = *loc;
}
status_t err = B_ERROR;
if (moveMode == kCreateRelativeLink) {
if (statbuf.st_dev == destNode.device) {
// relative link only works on the same device
char oldwd[B_PATH_NAME_LENGTH];
getcwd(oldwd, B_PATH_NAME_LENGTH);
BEntry destEntry;
destDir -> GetEntry(&destEntry);
BPath destPath;
destEntry.GetPath(&destPath);
chdir(destPath.Path());
// change working dir to target dir
BString destString(destPath.Path());
destString.Append("/");
BString srcString(path.Path());
srcString.RemoveLast(path.Leaf());
// find index while paths are the same
const char *src = srcString.String();
const char *dest = destString.String();
const char *lastFolderSrc = src;
const char *lastFolderDest = dest;
while (*src && *dest && *src == *dest) {
++src;
if (*dest++ == '/') {
lastFolderSrc = src;
lastFolderDest = dest;
}
}
src = lastFolderSrc;
dest = lastFolderDest;
BString source;
if (*dest == '\0' && *src != '\0') {
// source is deeper in the same tree than the target
source.Append(src);
} else if (*dest != '\0') {
// target is deeper in the same tree than the source
while (*dest) {
if (*dest == '/')
source.Prepend("../");
++dest;
}
source.Append(src);
}
// else source and target are in the same dir
source.Append(path.Leaf());
err = destDir->CreateSymLink(name, source.String(), &link);
chdir(oldwd);
// change working dir back to original
} else
moveMode = kCreateLink;
// fall back to absolute link mode
}
if (moveMode == kCreateLink)
err = destDir->CreateSymLink(name, path.Path(), &link);
if (err == B_UNSUPPORTED)
throw FailWithAlert(err, "The target disk does not support creating links.", NULL);
FailWithAlert::FailOnError(err, "Error creating link to \"%s\".", ref.name);
if (loc && loc != (BPoint *)-1)
link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo));
BNodeInfo nodeInfo(&link);
nodeInfo.SetType(B_LINK_MIMETYPE);
return B_OK;
}
// if move is on same volume don't copy
if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo
&& moveMode != kDuplicateSelection) {
// for "Move" the size for status is always 1 - since file
// size is irrelevant when simply moving to a new folder
thread_id thread = find_thread(NULL);
if (gStatusWindow && gStatusWindow->HasStatus(thread))
gStatusWindow->UpdateStatus(thread, ref.name, 1);
if (entry->IsDirectory())
return RecursiveMove(entry, destDir);
MoveError::FailOnError(entry->MoveTo(destDir, newName));
} else {
TrackerCopyLoopControl loopControl(find_thread(NULL));
bool makeOriginalName = (moveMode == kDuplicateSelection);
if (S_ISDIR(statbuf.st_mode)) {
CopyFolder(entry, destDir, &loopControl, loc, makeOriginalName, undo, moveMode == kMoveSelectionTo);
} else {
CopyFile(entry, &statbuf, destDir, &loopControl, loc, makeOriginalName, undo);
if (moveMode == kMoveSelectionTo)
entry->Remove();
}
}
} catch (status_t error) {
// no alert, was already taken care of before
return error;
} catch (MoveError error) {
BString errorString;
errorString << "Error moving \"" << ref.name << '"';
(new BAlert("", errorString.String(), "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
return error.fError;
} catch (FailWithAlert error) {
char buffer[256];
if (error.fName)
sprintf(buffer, error.fString, error.fName);
else
strcpy(buffer, error.fString);
(new BAlert("", buffer, "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
return error.fError;
}
return B_OK;
}
void
FSDuplicate(BObjectList<entry_ref> *srcList, BList *pointList)
{
LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList, (BEntry *)NULL,
pointList, kDuplicateSelection);
}
#if 0
status_t
FSCopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl,
BPoint *loc, bool makeOriginalName)
{
try {
CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName);
} catch (status_t error) {
return error;
}
return B_OK;
}
#endif
status_t
FSCopyAttributesAndStats(BNode *srcNode, BNode *destNode)
{
char *buffer = new char[1024];
// copy the attributes
srcNode->RewindAttrs();
char name[256];
while (srcNode->GetNextAttrName(name) == B_OK) {
attr_info info;
if (srcNode->GetAttrInfo(name, &info) != B_OK)
continue;
attr_info dest_info;
if (destNode->GetAttrInfo(name, &dest_info) == B_OK)
continue;
ssize_t bytes;
ssize_t numToRead = (ssize_t)info.size;
for (off_t offset = 0; numToRead > 0; offset += bytes) {
size_t chunkSize = (size_t)numToRead;
if (chunkSize > 1024)
chunkSize = 1024;
bytes = srcNode->ReadAttr(name, info.type, offset, buffer, chunkSize);
if (bytes <= 0)
break;
destNode->WriteAttr(name, info.type, offset, buffer, (size_t)bytes);
numToRead -= bytes;
}
}
delete[] buffer;
// copy the file stats
struct stat srcStat;
srcNode->GetStat(&srcStat);
destNode->SetPermissions(srcStat.st_mode);
destNode->SetOwner(srcStat.st_uid);
destNode->SetGroup(srcStat.st_gid);
destNode->SetModificationTime(srcStat.st_mtime);
destNode->SetCreationTime(srcStat.st_crtime);
return B_OK;
}
#if 0
status_t
FSCopyFile(BEntry* srcFile, StatStruct *srcStat, BDirectory* destDir,
CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName)
{
try {
CopyFile(srcFile, srcStat, destDir, loopControl, loc, makeOriginalName);
} catch (status_t error) {
return error;
}
return B_OK;
}
#endif
static status_t
MoveEntryToTrash(BEntry *entry, BPoint *loc, Undo &undo)
{
BDirectory trash_dir;
entry_ref ref;
status_t result = entry->GetRef(&ref);
if (result != B_OK)
return result;
node_ref nodeRef;
result = entry->GetNodeRef(&nodeRef);
if (result != B_OK)
return result;
StatStruct statbuf;
result = entry->GetStat(&statbuf);
if (entry->GetStat(&statbuf) != B_OK)
return result;
// if it's a directory close the window and any child dir windows
if (S_ISDIR(statbuf.st_mode)) {
BDirectory dir(entry);
// if it's a volume, try to unmount
if (dir.IsRootDirectory()) {
BVolume volume(nodeRef.device);
BVolume boot;
BVolumeRoster().GetBootVolume(&boot);
if (volume == boot) {
char name[B_FILE_NAME_LENGTH];
volume.GetName(name);
char buffer[256];
sprintf(buffer, "Cannot unmount the boot volume \"%s\".", name);
BAlert *alert = new BAlert("", buffer, "Cancel", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
} else {
BMessage message(kUnmountVolume);
message.AddInt32("device_id", volume.Device());
be_app->PostMessage(&message);
}
return B_OK;
}
// get trash directory on same volume as item being moved
result = FSGetTrashDir(&trash_dir, nodeRef.device);
if (result != B_OK)
return result;
// check hierarchy before moving
BEntry trashEntry;
trash_dir.GetEntry(&trashEntry);
if (dir == trash_dir || dir.Contains(&trashEntry)) {
(new BAlert("", "You cannot put the Trash, home or Desktop "
"directory into the trash.", "OK", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
// return no error so we don't get two dialogs
return B_OK;
}
BMessage message(kCloseWindowAndChildren);
node_ref parentNode;
parentNode.device = statbuf.st_dev;
parentNode.node = statbuf.st_ino;
message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref));
be_app->PostMessage(&message);
} else {
// get trash directory on same volume as item being moved
result = FSGetTrashDir(&trash_dir, nodeRef.device);
if (result != B_OK)
return result;
}
// make sure name doesn't conflict with anything in trash already
char name[B_FILE_NAME_LENGTH];
strcpy(name, ref.name);
if (trash_dir.Contains(name)) {
FSMakeOriginalName(name, &trash_dir, " copy");
undo.UpdateEntry(entry, name);
}
BNode *src_node = 0;
if (loc && loc != (BPoint *)-1
&& (src_node = GetWritableNode(entry, &statbuf)) != 0) {
trash_dir.GetStat(&statbuf);
PoseInfo poseInfo;
poseInfo.fInvisible = false;
poseInfo.fInitedDirectory = statbuf.st_ino;
poseInfo.fLocation = *loc;
src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
sizeof(poseInfo));
delete src_node;
}
BNode node(entry);
BPath path;
// Get path of entry before it's moved to the trash
// and write it to the file as an attribute
if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) {
BString originalPath(path.Path());
node.WriteAttrString(kAttrOriginalPath, &originalPath);
}
MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo);
return B_OK;
}
ConflictCheckResult
PreFlightNameCheck(BObjectList<entry_ref> *srcList, const BDirectory *destDir,
int32 *collisionCount, uint32 moveMode)
{
// count the number of name collisions in dest folder
*collisionCount = 0;
int32 count = srcList->CountItems();
for (int32 i = 0; i < count; i++) {
entry_ref *srcRef = srcList->ItemAt(i);
BEntry entry(srcRef);
BDirectory parent;
entry.GetParent(&parent);
if (parent != *destDir) {
if (destDir->Contains(srcRef->name))
(*collisionCount)++;
}
}
// prompt user only if there is more than one collision, otherwise the
// single collision case will be handled as a "Prompt" case by CheckName
if (*collisionCount > 0) {
const char *verb = (moveMode == kMoveSelectionTo) ? "moving" : "copying";
char replaceMsg[256];
sprintf(replaceMsg, kReplaceManyStr, verb, verb);
BAlert *alert = new BAlert("", replaceMsg,
"Cancel", "Prompt", "Replace All");
alert->SetShortcut(0, B_ESCAPE);
switch (alert->Go()) {
case 0:
return kCanceled;
case 1:
// user selected "Prompt"
return kPrompt;
case 2:
// user selected "Replace All"
return kReplaceAll;
}
}
return kNoConflicts;
}
void
FileStatToString(StatStruct *stat, char *buffer, int32 length)
{
tm timeData;
localtime_r(&stat->st_mtime, &timeData);
sprintf(buffer, "\n\t(%Ld bytes, ", stat->st_size);
uint32 pos = strlen(buffer);
strftime(buffer + pos, length - pos,"%b %d %Y, %I:%M:%S %p)", &timeData);
}
status_t
CheckName(uint32 moveMode, const BEntry *sourceEntry, const BDirectory *destDir,
bool multipleCollisions, ConflictCheckResult &replaceAll)
{
if (moveMode == kDuplicateSelection)
// when duplicating, we will never have a conflict
return B_OK;
// see if item already exists in destination dir
status_t err = B_OK;
char name[B_FILE_NAME_LENGTH];
sourceEntry->GetName(name);
bool sourceIsDirectory = sourceEntry->IsDirectory();
BDirectory srcDirectory;
if (sourceIsDirectory) {
srcDirectory.SetTo(sourceEntry);
BEntry destEntry;
destDir->GetEntry(&destEntry);
if (moveMode != kCreateLink
&& moveMode != kCreateRelativeLink
&& (srcDirectory == *destDir || srcDirectory.Contains(&destEntry))) {
(new BAlert("", "You can't move a folder into itself "
"or any of its own sub-folders.", "OK", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
return B_ERROR;
}
}
if (FSIsTrashDir(sourceEntry)) {
(new BAlert("", "You can't move or copy the trash.",
"OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
return B_ERROR;
}
BEntry entry;
if (destDir->FindEntry(name, &entry) != B_OK)
// no conflict, return
return B_OK;
if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) {
// if we are creating link in the same directory, the conflict will
// be handled later by giving the link a unique name
sourceEntry->GetParent(&srcDirectory);
if (srcDirectory == *destDir)
return B_OK;
}
bool destIsDir = entry.IsDirectory();
// be sure not to replace the parent directory of the item being moved
if (destIsDir) {
BDirectory test_dir(&entry);
if (test_dir.Contains(sourceEntry)) {
(new BAlert("", "You can't replace a folder "
"with one of its sub-folders.", "OK", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
return B_ERROR;
}
}
if (moveMode != kCreateLink
&& moveMode != kCreateRelativeLink
&& destIsDir != sourceIsDirectory) {
// ensure user isn't trying to replace a file with folder or vice versa
(new BAlert("", sourceIsDirectory
? "You cannot replace a file with a folder or a symbolic link."
: "You cannot replace a folder or a symbolic link with a file.",
"OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
return B_ERROR;
}
if (replaceAll != kReplaceAll) {
// prompt user to determine whether to replace or not
char replaceMsg[512];
if (moveMode == kCreateLink || moveMode == kCreateRelativeLink)
sprintf(replaceMsg, kSymLinkReplaceStr, name);
else if (sourceEntry->IsDirectory())
sprintf(replaceMsg, kDirectoryReplaceStr, name,
moveMode == kMoveSelectionTo ? "moving" : "copying");
else {
char sourceBuffer[96], destBuffer[96];
StatStruct statBuffer;
if (!sourceEntry->IsDirectory() && sourceEntry->GetStat(&statBuffer) == B_OK)
FileStatToString(&statBuffer, sourceBuffer, 96);
else
sourceBuffer[0] = '\0';
if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK)
FileStatToString(&statBuffer, destBuffer, 96);
else
destBuffer[0] = '\0';
sprintf(replaceMsg, kReplaceStr, name, destBuffer, name, sourceBuffer,
moveMode == kMoveSelectionTo ? "moving" : "copying");
}
// special case single collision (don't need Replace All shortcut)
BAlert *alert;
if (multipleCollisions || sourceIsDirectory) {
alert = new BAlert("", replaceMsg, "Skip", "Replace All");
} else {
alert = new BAlert("", replaceMsg, "Cancel", "Replace");
alert->SetShortcut(0, B_ESCAPE);
}
switch (alert->Go()) {
case 0: // user selected "Cancel" or "Skip"
replaceAll = kCanceled;
return B_ERROR;
case 1: // user selected "Replace" or "Replace All"
replaceAll = kReplaceAll;
// doesn't matter which since a single
// collision "Replace" is equivalent to a
// "Replace All"
break;
}
}
// delete destination item
if (!destIsDir) {
TrackerCopyLoopControl loopControl(find_thread(NULL));
err = entry.Remove();
} else
return B_OK;
if (err != B_OK) {
BString error;
error << "There was a problem trying to replace \""
<< name << "\". The item might be open or busy.";
BAlert *alert = new BAlert("", error.String(), "Cancel", 0, 0,
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
}
return err;
}
status_t
FSDeleteFolder(BEntry *dir_entry, CopyLoopControl *loopControl, bool update_status,
bool delete_top_dir, bool upateFileNameInStatus)
{
entry_ref ref;
BEntry entry;
BDirectory dir;
status_t err;
dir.SetTo(dir_entry);
dir.Rewind();
// loop through everything in folder and delete it, skipping trouble files
for (;;) {
if (dir.GetNextEntry(&entry) != B_OK)
break;
entry.GetRef(&ref);
if (loopControl->CheckUserCanceled())
return kTrashCanceled;
if (entry.IsDirectory())
err = FSDeleteFolder(&entry, loopControl, update_status, true,
upateFileNameInStatus);
else {
err = entry.Remove();
if (update_status)
loopControl->UpdateStatus(upateFileNameInStatus ? ref.name : "", ref, 1, true);
}
if (err == kTrashCanceled)
return kTrashCanceled;
else if (err == B_OK)
dir.Rewind();
else
loopControl->FileError(kFileDeleteErrorString, ref.name, err, false);
}
if (loopControl->CheckUserCanceled())
return kTrashCanceled;
dir_entry->GetRef(&ref);
if (update_status && delete_top_dir)
loopControl->UpdateStatus(NULL, ref, 1);
if (delete_top_dir)
return dir_entry->Remove();
else
return B_OK;
}
void
FSMakeOriginalName(BString &string, const BDirectory *destDir, const char *suffix)
{
if (!destDir->Contains(string.String()))
return;
FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH),
const_cast<BDirectory *>(destDir), suffix ? suffix : " copy");
string.UnlockBuffer();
}
void
FSMakeOriginalName(char *name, BDirectory *destDir, const char *suffix)
{
char root[B_FILE_NAME_LENGTH];
char copybase[B_FILE_NAME_LENGTH];
char temp_name[B_FILE_NAME_LENGTH + 10];
int32 fnum;
// is this name already original?
if (!destDir->Contains(name))
return;
// Determine if we're copying a 'copy'. This algorithm isn't perfect.
// If you're copying a file whose REAL name ends with 'copy' then
// this method will return "<filename> 1", not "<filename> copy"
// However, it will correctly handle file that contain 'copy'
// elsewhere in their name.
bool copycopy = false; // are we copying a copy?
int32 len = (int32)strlen(name);
char *p = name + len - 1; // get pointer to end os name
// eat up optional numbers (if were copying "<filename> copy 34")
while ((p > name) && isdigit(*p))
p--;
// eat up optional spaces
while ((p > name) && isspace(*p))
p--;
// now look for the phrase " copy"
if (p > name) {
// p points to the last char of the word. For example, 'y' in 'copy'
if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) {
// we found 'copy' in the right place.
// so truncate after 'copy'
*(p + 1) = '\0';
copycopy = true;
// save the 'root' name of the file, for possible later use.
// that is copy everything but trailing " copy". Need to
// NULL terminate after copy
strncpy(root, name, (uint32)((p - name) - 4));
root[(p - name) - 4] = '\0';
}
}
if (!copycopy) {
/*
The name can't be longer than B_FILE_NAME_LENGTH.
The algoritm adds " copy XX" to the name. That's 8 characters.
B_FILE_NAME_LENGTH already accounts for NULL termination so we
don't need to save an extra char at the end.
*/
if (strlen(name) > B_FILE_NAME_LENGTH - 8) {
// name is too long - truncate it!
name[B_FILE_NAME_LENGTH - 8] = '\0';
}
strcpy(root, name); // save root name
strcat(name, suffix);
}
strcpy(copybase, name);
// if name already exists then add a number
fnum = 1;
strcpy(temp_name, name);
while (destDir->Contains(temp_name)) {
sprintf(temp_name, "%s %ld", copybase, ++fnum);
if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) {
/*
The name has grown too long. Maybe we just went from
"<filename> copy 9" to "<filename> copy 10" and that extra
character was too much. The solution is to further
truncate the 'root' name and continue.
??? should we reset fnum or not ???
*/
root[strlen(root) - 1] = '\0';
sprintf(temp_name, "%s%s %ld", root, suffix, fnum);
}
}
ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1)));
strcpy(name, temp_name);
}
status_t
FSRecursiveCalcSize(BInfoWindow *wind, BDirectory *dir, off_t *running_size,
int32 *fileCount, int32 *dirCount)
{
thread_id tid = find_thread(NULL);
dir->Rewind();
BEntry entry;
while (dir->GetNextEntry(&entry) == B_OK) {
// be sure window hasn't closed
if (wind && wind->StopCalc())
return B_OK;
if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(tid))
return kUserCanceled;
StatStruct statbuf;
entry.GetStat(&statbuf);
if (S_ISDIR(statbuf.st_mode)) {
BDirectory subdir(&entry);
(*dirCount)++;
(*running_size) += 1024;
status_t result;
if ((result = FSRecursiveCalcSize(wind, &subdir, running_size,
fileCount, dirCount)) != B_OK)
return result;
} else {
(*fileCount)++;
(*running_size) += statbuf.st_size + 1024; // Add to compensate
// for attributes.
}
}
return B_OK;
}
status_t
CalcItemsAndSize(BObjectList<entry_ref> *refList, int32 *totalCount, off_t *totalSize)
{
int32 fileCount = 0;
int32 dirCount = 0;
thread_id tid = find_thread(NULL);
int32 num_items = refList->CountItems();
for (int32 i = 0; i < num_items; i++) {
entry_ref *ref = refList->ItemAt(i);
BEntry entry(ref);
StatStruct statbuf;
entry.GetStat(&statbuf);
if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(tid))
return kUserCanceled;
if (S_ISDIR(statbuf.st_mode)) {
BDirectory dir(&entry);
dirCount++;
(*totalSize) += 1024;
status_t result;
if ((result = FSRecursiveCalcSize(NULL, &dir, totalSize, &fileCount,
&dirCount)) != B_OK)
return result;
} else {
fileCount++;
(*totalSize) += statbuf.st_size + 1024;
}
}
*totalCount += (fileCount + dirCount);
return B_OK;
}
status_t
FSGetTrashDir(BDirectory *trash_dir, dev_t dev)
{
BVolume volume(dev);
status_t result = volume.InitCheck();
if (result != B_OK)
return result;
BPath path;
result = find_directory(B_TRASH_DIRECTORY, &path, true, &volume);
if (result != B_OK)
return result;
result = trash_dir->SetTo(path.Path());
if (result != B_OK)
return result;
// make trash invisible
attr_info a_info;
if (trash_dir->GetAttrInfo(kAttrPoseInfo, &a_info) != B_OK) {
StatStruct sbuf;
trash_dir->GetStat(&sbuf);
// move trash to bottom left of main screen initially
BScreen screen(B_MAIN_SCREEN_ID);
BRect scrn_frame = screen.Frame();
PoseInfo poseInfo;
poseInfo.fInvisible = false;
poseInfo.fInitedDirectory = sbuf.st_ino;
poseInfo.fLocation = BPoint(scrn_frame.left + 20, scrn_frame.bottom - 60);
trash_dir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo,
sizeof(PoseInfo));
}
return B_OK;
}
status_t
FSGetDeskDir(BDirectory *deskDir, dev_t dev)
{
BVolume volume(dev);
status_t result = volume.InitCheck();
if (result != B_OK)
return result;
BPath path;
result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume);
if (result != B_OK)
return result;
result = deskDir->SetTo(path.Path());
if (result != B_OK)
return result;
// make desktop invisible
PoseInfo poseInfo;
poseInfo.fInvisible = true;
poseInfo.fInitedDirectory = -1LL;
deskDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo));
size_t size;
const void* data = GetTrackerResources()->
LoadResource('ICON', kResDeskIcon, &size);
if (data != NULL)
deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
data = GetTrackerResources()->
LoadResource('MICN', kResDeskIcon, &size);
if (data != NULL)
deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
#ifdef __HAIKU__
data = GetTrackerResources()->
LoadResource(B_VECTOR_ICON_TYPE, kResDeskIcon, &size);
if (data != NULL)
deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
#endif
return B_OK;
}
status_t
FSGetBootDeskDir(BDirectory *deskDir)
{
BVolume bootVol;
BVolumeRoster().GetBootVolume(&bootVol);
BPath path;
status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &bootVol);
if (result != B_OK)
return result;
return deskDir->SetTo(path.Path());
}
static bool
FSIsDirFlavor(const BEntry *entry, directory_which directoryType)
{
StatStruct dir_stat;
StatStruct entry_stat;
BVolume volume;
BPath path;
if (entry->GetStat(&entry_stat) != B_OK)
return false;
if (volume.SetTo(entry_stat.st_dev) != B_OK)
return false;
if (find_directory(directoryType, &path, false, &volume) != B_OK)
return false;
stat(path.Path(), &dir_stat);
return dir_stat.st_ino == entry_stat.st_ino
&& dir_stat.st_dev == entry_stat.st_dev;
}
bool
FSIsPrintersDir(const BEntry *entry)
{
return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY);
}
bool
FSIsTrashDir(const BEntry *entry)
{
return FSIsDirFlavor(entry, B_TRASH_DIRECTORY);
}
bool
FSIsDeskDir(const BEntry *entry, dev_t device)
{
BVolume volume(device);
status_t result = volume.InitCheck();
if (result != B_OK)
return false;
BPath path;
result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume);
if (result != B_OK)
return false;
BEntry entryToCompare(path.Path());
return entryToCompare == *entry;
}
bool
FSIsDeskDir(const BEntry *entry)
{
entry_ref ref;
if (entry->GetRef(&ref) != B_OK)
return false;
return FSIsDeskDir(entry, ref.device);
}
bool
FSIsHomeDir(const BEntry *entry)
{
return FSIsDirFlavor(entry, B_USER_DIRECTORY);
}
bool
DirectoryMatchesOrContains(const BEntry *entry, directory_which which)
{
BPath path;
if (find_directory(which, &path, false, NULL) != B_OK)
return false;
BEntry dirEntry(path.Path());
if (dirEntry.InitCheck() != B_OK)
return false;
if (dirEntry == *entry)
// root level match
return true;
BDirectory dir(&dirEntry);
return dir.Contains(entry);
}
bool
DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath,
directory_which which)
{
BPath path;
if (find_directory(which, &path, false, NULL) != B_OK)
return false;
path.Append(additionalPath);
BEntry dirEntry(path.Path());
if (dirEntry.InitCheck() != B_OK)
return false;
if (dirEntry == *entry)
// root level match
return true;
BDirectory dir(&dirEntry);
return dir.Contains(entry);
}
bool
DirectoryMatches(const BEntry *entry, directory_which which)
{
BPath path;
if (find_directory(which, &path, false, NULL) != B_OK)
return false;
BEntry dirEntry(path.Path());
if (dirEntry.InitCheck() != B_OK)
return false;
return dirEntry == *entry;
}
bool
DirectoryMatches(const BEntry *entry, const char *additionalPath, directory_which which)
{
BPath path;
if (find_directory(which, &path, false, NULL) != B_OK)
return false;
path.Append(additionalPath);
BEntry dirEntry(path.Path());
if (dirEntry.InitCheck() != B_OK)
return false;
return dirEntry == *entry;
}
extern status_t
FSFindTrackerSettingsDir(BPath *path, bool autoCreate)
{
status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path, autoCreate);
if (result != B_OK)
return result;
path->Append("Tracker");
return mkdir(path->Path(), 0777) ? B_OK : errno;
}
bool
FSInTrashDir(const entry_ref *ref)
{
BEntry entry(ref);
if (entry.InitCheck() != B_OK)
return false;
BDirectory trashDir;
if (FSGetTrashDir(&trashDir, ref->device) != B_OK)
return false;
return trashDir.Contains(&entry);
}
void
FSEmptyTrash()
{
if (find_thread("_tracker_empty_trash_") != B_OK) {
resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_",
B_NORMAL_PRIORITY, NULL));
}
}
status_t
empty_trash(void *)
{
// empty trash on all mounted volumes
status_t err = B_OK;
thread_id thread = find_thread(NULL);
if (gStatusWindow)
gStatusWindow->CreateStatusItem(thread, kTrashState);
// calculate the sum total of all items on all volumes in trash
BObjectList<entry_ref> srcList;
int32 totalCount = 0;
off_t totalSize = 0;
BVolumeRoster volumeRoster;
BVolume volume;
while (volumeRoster.GetNextVolume(&volume) == B_OK) {
if (volume.IsReadOnly() || !volume.IsPersistent())
continue;
BDirectory trashDirectory;
if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
continue;
BEntry entry;
trashDirectory.GetEntry(&entry);
entry_ref ref;
entry.GetRef(&ref);
srcList.AddItem(&ref);
err = CalcItemsAndSize(&srcList, &totalCount, &totalSize);
if (err != B_OK)
break;
srcList.MakeEmpty();
// don't count trash directory itself
totalCount--;
}
if (err == B_OK) {
if (gStatusWindow)
gStatusWindow->InitStatusItem(thread, totalCount, totalCount);
volumeRoster.Rewind();
while (volumeRoster.GetNextVolume(&volume) == B_OK) {
TrackerCopyLoopControl loopControl(thread);
if (volume.IsReadOnly() || !volume.IsPersistent())
continue;
BDirectory trashDirectory;
if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK)
continue;
BEntry entry;
trashDirectory.GetEntry(&entry);
err = FSDeleteFolder(&entry, &loopControl, true, false);
}
}
if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) {
(new BAlert("", "Error emptying Trash!", "OK", NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
}
if (gStatusWindow)
gStatusWindow->RemoveStatusItem(find_thread(NULL));
return B_OK;
}
status_t
_DeleteTask(BObjectList<entry_ref> *list, bool confirm)
{
if (confirm) {
bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash();
if (!dontMoveToTrash) {
BAlert *alert = new BAlert("", kDeleteConfirmationStr,
"Cancel", "Move to Trash", "Delete", B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->SetShortcut(1, 'm');
alert->SetShortcut(2, 'd');
switch (alert->Go()) {
case 0:
delete list;
return B_OK;
case 1:
FSMoveToTrash(list, NULL, false);
return B_OK;
}
} else {
BAlert *alert = new BAlert("", kDeleteConfirmationStr,
"Cancel", "Delete", NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->SetShortcut(1, 'd');
if (!alert->Go()) {
delete list;
return B_OK;
}
}
}
thread_id thread = find_thread(NULL);
if (gStatusWindow)
gStatusWindow->CreateStatusItem(thread, kDeleteState);
// calculate the sum total of all items on all volumes in trash
int32 totalItems = 0;
int64 totalSize = 0;
status_t err = CalcItemsAndSize(list, &totalItems, &totalSize);
if (err == B_OK) {
if (gStatusWindow)
gStatusWindow->InitStatusItem(thread, totalItems, totalItems);
int32 count = list->CountItems();
TrackerCopyLoopControl loopControl(thread);
for (int32 index = 0; index < count; index++) {
entry_ref ref(*list->ItemAt(index));
BEntry entry(&ref);
loopControl.UpdateStatus(ref.name, ref, 1, true);
if (entry.IsDirectory())
err = FSDeleteFolder(&entry, &loopControl, true, true, true);
else
err = entry.Remove();
}
if (err != kTrashCanceled && err != kUserCanceled && err != B_OK)
(new BAlert("", "Error Deleting items", "OK", NULL, NULL,
B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
}
if (gStatusWindow)
gStatusWindow->RemoveStatusItem(find_thread(NULL));
delete list;
return B_OK;
}
status_t
FSRecursiveCreateFolder(BPath path)
{
BEntry entry(path.Path());
if (entry.InitCheck() != B_OK) {
BPath parentPath;
status_t err = path.GetParent(&parentPath);
if (err != B_OK)
return err;
err = FSRecursiveCreateFolder(parentPath);
if (err != B_OK)
return err;
}
entry.SetTo(path.Path());
if (entry.Exists())
return B_FILE_EXISTS;
else {
char name[B_FILE_NAME_LENGTH];
BDirectory parent;
entry.GetParent(&parent);
entry.GetName(name);
parent.CreateDirectory(name, NULL);
}
return B_OK;
}
status_t
_RestoreTask(BObjectList<entry_ref> *list)
{
thread_id thread = find_thread(NULL);
if (gStatusWindow)
gStatusWindow->CreateStatusItem(thread, kRestoreFromTrashState);
// calculate the sum total of all items that will be restored
int32 totalItems = 0;
int64 totalSize = 0;
status_t err = CalcItemsAndSize(list, &totalItems, &totalSize);
if (err == B_OK) {
if (gStatusWindow)
gStatusWindow->InitStatusItem(thread, totalItems, totalItems);
int32 count = list->CountItems();
TrackerCopyLoopControl loopControl(thread);
for (int32 index = 0; index < count; index++) {
entry_ref ref(*list->ItemAt(index));
BEntry entry(&ref);
BPath originalPath;
loopControl.UpdateStatus(ref.name, ref, 1, true);
if (FSGetOriginalPath(&entry, &originalPath) != B_OK)
continue;
BEntry originalEntry(originalPath.Path());
BPath parentPath;
err = originalPath.GetParent(&parentPath);
if (err != B_OK)
continue;
BEntry parentEntry(parentPath.Path());
if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) {
if (FSRecursiveCreateFolder(parentPath) == B_OK) {
originalEntry.SetTo(originalPath.Path());
if (entry.InitCheck() != B_OK)
continue;
}
}
if (!originalEntry.Exists()) {
BDirectory dir(parentPath.Path());
if (dir.InitCheck() == B_OK) {
char leafName[B_FILE_NAME_LENGTH];
originalEntry.GetName(leafName);
if (entry.MoveTo(&dir, leafName) == B_OK) {
BNode node(&entry);
if (node.InitCheck() == B_OK)
node.RemoveAttr(kAttrOriginalPath);
}
}
}
err = loopControl.CheckUserCanceled();
if (err != B_OK)
break;
}
}
if (gStatusWindow)
gStatusWindow->RemoveStatusItem(find_thread(NULL));
delete list;
return err;
}
void
FSCreateTrashDirs()
{
BVolume volume;
BVolumeRoster roster;
roster.Rewind();
while (roster.GetNextVolume(&volume) == B_OK) {
if (volume.IsReadOnly() || !volume.IsPersistent())
continue;
BPath path;
find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume);
find_directory(B_TRASH_DIRECTORY, &path, true, &volume);
BDirectory trashDir;
if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
size_t size;
const void* data = GetTrackerResources()->
LoadResource('ICON', kResTrashIcon, &size);
if (data != NULL) {
trashDir.WriteAttr(kAttrLargeIcon, 'ICON', 0,
data, size);
}
data = GetTrackerResources()->
LoadResource('MICN', kResTrashIcon, &size);
if (data != NULL) {
trashDir.WriteAttr(kAttrMiniIcon, 'MICN', 0,
data, size);
}
#ifdef __HAIKU
data = GetTrackerResources()->
LoadResource(B_VECTOR_ICON_TYPE, kResTrashIcon, &size);
if (data != NULL) {
trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0,
data, size);
}
#endif
}
}
}
status_t
FSCreateNewFolder(const entry_ref *ref)
{
node_ref node;
node.device = ref->device;
node.node = ref->directory;
BDirectory dir(&node);
status_t result = dir.InitCheck();
if (result != B_OK)
return result;
// ToDo: is that really necessary here?
BString name(ref->name);
FSMakeOriginalName(name, &dir, "-");
BDirectory newDir;
result = dir.CreateDirectory(name.String(), &newDir);
if (result != B_OK)
return result;
BNodeInfo nodeInfo(&newDir);
nodeInfo.SetType(B_DIR_MIMETYPE);
return result;
}
status_t
FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef,
node_ref *newNode)
{
BDirectory dir(dirNode);
status_t result = dir.InitCheck();
if (result == B_OK) {
char name[B_FILE_NAME_LENGTH];
strcpy(name, "New Folder");
int32 fnum = 1;
while (dir.Contains(name)) {
// if base name already exists then add a number
// ToDo:
// move this logic ot FSMakeOriginalName
if (++fnum > 9)
sprintf(name, "New Folder%ld", fnum);
else
sprintf(name, "New Folder %ld", fnum);
}
BDirectory newDir;
result = dir.CreateDirectory(name, &newDir);
if (result == B_OK) {
BEntry entry;
newDir.GetEntry(&entry);
entry.GetRef(newRef);
entry.GetNodeRef(newNode);
BNodeInfo nodeInfo(&newDir);
nodeInfo.SetType(B_DIR_MIMETYPE);
// add undo item
NewFolderUndo undo(*newRef);
return B_OK;
}
}
BAlert *alert = new BAlert("", "Sorry, could not create a new folder.",
"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
return result;
}
ReadAttrResult
ReadAttr(const BNode *node, const char *hostAttrName, const char *foreignAttrName,
type_code type, off_t offset, void *buffer, size_t length,
void (*swapFunc)(void *), bool isForeign)
{
if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, length) == (ssize_t)length)
return kReadAttrNativeOK;
// PRINT(("trying %s\n", foreignAttrName));
// try the other endianness
if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) != (ssize_t)length)
return kReadAttrFailed;
// PRINT(("got %s\n", foreignAttrName));
if (!swapFunc)
return kReadAttrForeignOK;
(swapFunc)(buffer);
// run the endian swapper
return kReadAttrForeignOK;
}
ReadAttrResult
GetAttrInfo(const BNode *node, const char *hostAttrName, const char *foreignAttrName,
type_code *type, size_t *size)
{
attr_info info;
if (node->GetAttrInfo(hostAttrName, &info) == B_OK) {
if (type)
*type = info.type;
if (size)
*size = (size_t)info.size;
return kReadAttrNativeOK;
}
if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) {
if (type)
*type = info.type;
if (size)
*size = (size_t)info.size;
return kReadAttrForeignOK;
}
return kReadAttrFailed;
}
// launching code
static status_t
TrackerOpenWith(const BMessage *refs)
{
BMessage clone(*refs);
ASSERT(dynamic_cast<TTracker *>(be_app));
ASSERT(clone.what);
clone.AddInt32("launchUsingSelector", 0);
// runs the Open With window
be_app->PostMessage(&clone);
return B_OK;
}
static void
AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on),
const entry_ref *entry, const BMessage *message, bool on)
{
Thread::Launch(NewFunctionObject(func, entry, message, on),
B_NORMAL_PRIORITY, "LaunchTask");
}
static bool
SniffIfGeneric(const entry_ref *ref)
{
BNode node(ref);
char type[B_MIME_TYPE_LENGTH];
BNodeInfo info(&node);
if (info.GetType(type) == B_OK && strcasecmp(type, B_FILE_MIME_TYPE) != 0)
// already has a type and it's not octet stream
return false;
BPath path(ref);
if (path.Path()) {
// force a mimeset
node.RemoveAttr(kAttrMIMEType);
update_mime_info(path.Path(), 0, 1, 1);
}
return true;
}
static void
SniffIfGeneric(const BMessage *refs)
{
entry_ref ref;
for (int32 index = 0; ; index++) {
if (refs->FindRef("refs", index, &ref) != B_OK)
break;
SniffIfGeneric(&ref);
}
}
static void
_TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs, bool openWithOK)
{
team_id team;
status_t error = B_ERROR;
BString alertString;
for (int32 mimesetIt = 0; ; mimesetIt++) {
error = be_roster->Launch(appRef, refs, &team);
if (error == B_ALREADY_RUNNING)
// app already running, not really an error
error = B_OK;
if (error == B_OK)
break;
if (mimesetIt > 0)
break;
// failed to open, try mimesetting the refs and launching again
SniffIfGeneric(refs);
}
if (error == B_OK) {
// close possible parent window, if specified
const node_ref *nodeToClose = 0;
int32 numBytes;
refs->FindData("nodeRefsToClose", B_RAW_TYPE, (const void **)&nodeToClose, &numBytes);
if (nodeToClose)
dynamic_cast<TTracker *>(be_app)->CloseParent(*nodeToClose);
} else {
alertString << "Could not open \"" << appRef->name << "\" (" << strerror(error) << "). ";
if (refs && openWithOK) {
alertString << kFindAlternativeStr;
BAlert *alert = new BAlert("", alertString.String(),
"Cancel", "Find", 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
if (alert->Go() == 1)
error = TrackerOpenWith(refs);
} else {
BAlert *alert = new BAlert("", alertString.String(),
"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
}
}
}
extern "C" char** environ;
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
extern "C" status_t _kern_load_image(const char * const *flatArgs,
size_t flatArgsSize, int32 argCount, int32 envCount, int32 priority,
uint32 flags, port_id errorPort, uint32 errorToken);
extern "C" status_t __flatten_process_args(const char * const *args,
int32 argCount, const char * const *env, int32 envCount, char ***_flatArgs,
size_t *_flatSize);
#else
extern "C"
# if !B_BEOS_VERSION_DANO
_IMPEXP_ROOT
# endif
status_t _kload_image_etc_(int argc, char **argv, char **envp,
char *buf, int bufsize);
#endif
static status_t
LoaderErrorDetails(const entry_ref *app, BString &details)
{
BPath path;
BEntry appEntry(app, true);
status_t result = appEntry.GetPath(&path);
if (result != B_OK)
return result;
char *argv[2] = { const_cast<char *>(path.Path()), 0};
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
port_id errorPort = create_port(1, "Tracker loader error");
// count environment variables
uint32 envCount = 0;
while (environ[envCount] != NULL)
envCount++;
char** flatArgs = NULL;
size_t flatArgsSize;
result = __flatten_process_args((const char **)argv, 1,
environ, envCount, &flatArgs, &flatArgsSize);
if (result != B_OK)
return result;
result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount,
B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0);
if (result == B_OK) {
// we weren't supposed to be able to start the application...
return B_ERROR;
}
// read error message from port and construct details string
ssize_t bufferSize;
do {
bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0);
} while (bufferSize == B_INTERRUPTED);
if (bufferSize <= B_OK) {
delete_port(errorPort);
return bufferSize;
}
uint8 *buffer = (uint8 *)malloc(bufferSize);
if (buffer == NULL) {
delete_port(errorPort);
return B_NO_MEMORY;
}
bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize,
B_RELATIVE_TIMEOUT, 0);
delete_port(errorPort);
if (bufferSize < B_OK) {
free(buffer);
return bufferSize;
}
BMessage message;
result = message.Unflatten((const char *)buffer);
free(buffer);
if (result != B_OK)
return result;
int32 errorCode = B_ERROR;
result = message.FindInt32("error", &errorCode);
if (result != B_OK)
return result;
const char *detailName = NULL;
switch (errorCode) {
case B_MISSING_LIBRARY:
detailName = "missing library";
break;
case B_MISSING_SYMBOL:
detailName = "missing symbol";
break;
}
if (detailName == NULL)
return B_ERROR;
const char *detail;
for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK; i++) {
if (i > 0)
details += ", ";
details += detail;
}
return B_OK;
#else
result = _kload_image_etc_(1, argv, environ, details.LockBuffer(1024), 1024);
details.UnlockBuffer();
#endif
return B_OK;
}
static void
_TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs,
bool openWithOK)
{
BMessage copyOfRefs(*refs);
entry_ref documentRef;
if (copyOfRefs.FindRef("refs", &documentRef) != B_OK)
// nothing to launch, we are done
return;
status_t error = B_ERROR;
entry_ref app;
BMessage *refsToPass = NULL;
BString alertString;
const char *alternative = 0;
for (int32 mimesetIt = 0; ; mimesetIt++) {
alertString = "";
error = be_roster->FindApp(&documentRef, &app);
if (error != B_OK && mimesetIt == 0) {
SniffIfGeneric(&copyOfRefs);
continue;
}
if (error != B_OK) {
alertString << "Could not find an application to open \"" << documentRef.name
<< "\" (" << strerror(error) << "). ";
if (openWithOK)
alternative = kFindApplicationStr;
break;
} else {
BEntry appEntry(&app, true);
for (int32 index = 0;;) {
// remove the app itself from the refs received so we don't try
// to open ourselves
entry_ref ref;
if (copyOfRefs.FindRef("refs", index, &ref) != B_OK)
break;
// deal with symlinks properly
BEntry documentEntry(&ref, true);
if (appEntry == documentEntry) {
PRINT(("stripping %s, app %s \n", ref.name, app.name));
copyOfRefs.RemoveData("refs", index);
} else {
PRINT(("leaving %s, app %s \n", ref.name, app.name));
index++;
}
}
refsToPass = CountRefs(&copyOfRefs) > 0 ? &copyOfRefs: 0;
team_id team;
error = be_roster->Launch(&app, refsToPass, &team);
if (error == B_ALREADY_RUNNING)
// app already running, not really an error
error = B_OK;
if (error == B_OK || mimesetIt != 0)
break;
SniffIfGeneric(&copyOfRefs);
}
}
if (error != B_OK && alertString.Length() == 0) {
BString loaderErrorString;
bool openedDocuments = true;
if (!refsToPass) {
// we just double clicked the app itself, do not offer to
// find a handling app
openWithOK = false;
openedDocuments = false;
}
if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) {
alertString << "Could not open \"" << app.name
<< "\". The file is mistakenly marked as executable. ";
if (!openWithOK) {
// offer the possibility to change the permissions
alertString << "\nShould this be fixed?";
BAlert *alert = new BAlert("", alertString.String(),
"Cancel", "Proceed", 0, B_WIDTH_AS_USUAL,
B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
if (alert->Go() == 1) {
BEntry entry(&documentRef);
mode_t permissions;
error = entry.GetPermissions(&permissions);
if (error == B_OK)
error = entry.SetPermissions(permissions & ~(S_IXUSR | S_IXGRP | S_IXOTH));
if (error == B_OK) {
// we updated the permissions, so let's try again
_TrackerLaunchDocuments(NULL, refs, false);
return;
} else {
alertString = "Could not update permissions of file \"";
alertString << app.name << "\". " << strerror(error);
}
} else
return;
}
alternative = kFindApplicationStr;
} else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) {
alertString << "Could not open \"" << documentRef.name
<< "\" because application \"" << app.name << "\" is in the trash. ";
alternative = kFindAlternativeStr;
} else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) {
alertString << "Could not open \"" << documentRef.name << "\" "
<< "(" << strerror(error) << "). ";
alternative = kFindAlternativeStr;
} else if (error == B_MISSING_SYMBOL
&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
alertString << "Could not open \"" << documentRef.name << "\" ";
if (openedDocuments)
alertString << "with application \"" << app.name << "\" ";
alertString << "(Missing symbol: " << loaderErrorString << "). \n";
alternative = kFindAlternativeStr;
} else if (error == B_MISSING_LIBRARY
&& LoaderErrorDetails(&app, loaderErrorString) == B_OK) {
alertString << "Could not open \"" << documentRef.name << "\" ";
if (openedDocuments)
alertString << "with application \"" << app.name << "\" ";
alertString << "(Missing libraries: " << loaderErrorString << "). \n";
alternative = kFindAlternativeStr;
} else {
alertString << "Could not open \"" << documentRef.name
<< "\" with application \"" << app.name << "\" (" << strerror(error) << "). ";
alternative = kFindAlternativeStr;
}
}
if (error != B_OK) {
if (openWithOK) {
ASSERT(alternative);
alertString << alternative;
BAlert *alert = new BAlert("", alertString.String(),
"Cancel", "Find", 0, B_WIDTH_AS_USUAL,
B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
if (alert->Go() == 1)
error = TrackerOpenWith(refs);
} else {
BAlert *alert = new BAlert("", alertString.String(),
"Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
alert->Go();
}
}
}
// the following three calls don't return any reasonable error codes,
// should fix that, making them void
status_t
TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async, bool openWithOK)
{
if (!async)
_TrackerLaunchAppWithDocuments(appRef, refs, openWithOK);
else
AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, openWithOK);
return B_OK;
}
status_t
TrackerLaunch(const entry_ref *appRef, bool async)
{
if (!async)
_TrackerLaunchAppWithDocuments(appRef, 0, false);
else
AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false);
return B_OK;
}
status_t
TrackerLaunch(const BMessage *refs, bool async, bool openWithOK)
{
if (!async)
_TrackerLaunchDocuments(0, refs, openWithOK);
else
AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK);
return B_OK;
}
status_t
LaunchBrokenLink(const char *signature, const BMessage *refs)
{
// This call is to support a hacky workaround for double-clicking
// broken refs for cifs
be_roster->Launch(signature, const_cast<BMessage *>(refs));
return B_OK;
}
// external launch calls; need to be robust, work if Tracker is not running
#if !B_BEOS_VERSION_DANO
_IMPEXP_TRACKER
#endif
status_t
FSLaunchItem(const entry_ref *application, const BMessage *refsReceived,
bool async, bool openWithOK)
{
return TrackerLaunch(application, refsReceived, async, openWithOK);
}
#if !B_BEOS_VERSION_DANO
_IMPEXP_TRACKER
#endif
status_t
FSOpenWith(BMessage *listOfRefs)
{
status_t result = B_ERROR;
listOfRefs->what = B_REFS_RECEIVED;
if (dynamic_cast<TTracker *>(be_app))
result = TrackerOpenWith(listOfRefs);
else
ASSERT(!"not yet implemented");
return result;
}
// legacy calls, need for compatibility
void
FSOpenWithDocuments(const entry_ref *executable, BMessage *documents)
{
TrackerLaunch(executable, documents, true);
delete documents;
}
status_t
FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs)
{
BMessage temp(B_REFS_RECEIVED);
if (!listOfRefs) {
ASSERT(ref);
temp.AddRef("refs", ref);
listOfRefs = &temp;
}
FSOpenWith(listOfRefs);
return B_OK;
}
status_t
FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async)
{
if (message)
message->what = B_REFS_RECEIVED;
status_t result = TrackerLaunch(ref, message, async, true);
delete message;
return result;
}
void
FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace)
{
FSLaunchItem(ref, message, workspace, true);
}
// Get the original path of an entry in the trash
status_t
FSGetOriginalPath(BEntry *entry, BPath *result)
{
status_t err;
entry_ref ref;
err = entry->GetRef(&ref);
if (err != B_OK)
return err;
// Only call the routine for entries in the trash
if (!FSInTrashDir(&ref))
return B_ERROR;
BNode node(entry);
BString originalPath;
if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) {
// We're in luck, the entry has the original path in an attribute
err = result->SetTo(originalPath.String());
return err;
}
// Iterate the parent directories to find one with
// the original path attribute
BEntry parent(*entry);
err = parent.InitCheck();
if (err != B_OK)
return err;
// walk up the directory structure until we find a node
// with original path attribute
do {
// move to the parent of this node
err = parent.GetParent(&parent);
if (err != B_OK)
return err;
// return if we are at the root of the trash
if (FSIsTrashDir(&parent))
return B_ENTRY_NOT_FOUND;
// get the parent as a node
err = node.SetTo(&parent);
if (err != B_OK)
return err;
} while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK);
// Found the attribute, figure out there this file
// used to live, based on the successfully-read attribute
err = result->SetTo(originalPath.String());
if (err != B_OK)
return err;
BPath path, pathParent;
err = parent.GetPath(&pathParent);
if (err != B_OK)
return err;
err = entry->GetPath(&path);
if (err != B_OK)
return err;
result->Append(path.Path() + strlen(pathParent.Path()) + 1);
// compute the new path by appending the offset of
// the item we are locating, to the original path
// of the parent
return B_OK;
}
directory_which
WellKnowEntryList::Match(const node_ref *node)
{
const WellKnownEntry *result = MatchEntry(node);
if (result)
return result->which;
return (directory_which)-1;
}
const WellKnowEntryList::WellKnownEntry *
WellKnowEntryList::MatchEntry(const node_ref *node)
{
if (!self)
self = new WellKnowEntryList();
return self->MatchEntryCommon(node);
}
const WellKnowEntryList::WellKnownEntry *
WellKnowEntryList::MatchEntryCommon(const node_ref *node)
{
uint32 count = entries.size();
for (uint32 index = 0; index < count; index++)
if (*node == entries[index].node)
return &entries[index];
return NULL;
}
void
WellKnowEntryList::Quit()
{
delete self;
self = NULL;
}
void
WellKnowEntryList::AddOne(directory_which which, const char *name)
{
BPath path;
if (find_directory(which, &path, true) != B_OK)
return;
BEntry entry(path.Path(), true);
node_ref node;
if (entry.GetNodeRef(&node) != B_OK)
return;
entries.push_back(WellKnownEntry(&node, which, name));
}
void
WellKnowEntryList::AddOne(directory_which which, directory_which base,
const char *extra, const char *name)
{
BPath path;
if (find_directory(base, &path, true) != B_OK)
return;
path.Append(extra);
BEntry entry(path.Path(), true);
node_ref node;
if (entry.GetNodeRef(&node) != B_OK)
return;
entries.push_back(WellKnownEntry(&node, which, name));
}
void
WellKnowEntryList::AddOne(directory_which which, const char *path, const char *name)
{
BEntry entry(path, true);
node_ref node;
if (entry.GetNodeRef(&node) != B_OK)
return;
entries.push_back(WellKnownEntry(&node, which, name));
}
WellKnowEntryList::WellKnowEntryList()
{
#ifdef __HAIKU__
AddOne(B_SYSTEM_DIRECTORY, "system");
#else
AddOne(B_BEOS_DIRECTORY, "beos");
AddOne(B_BEOS_SYSTEM_DIRECTORY, "system");
#endif
AddOne((directory_which)B_BOOT_DISK, "/boot", "boot");
AddOne(B_USER_DIRECTORY, "home");
AddOne(B_BEOS_FONTS_DIRECTORY, "fonts");
AddOne(B_COMMON_FONTS_DIRECTORY, "fonts");
AddOne(B_USER_FONTS_DIRECTORY, "fonts");
AddOne(B_BEOS_APPS_DIRECTORY, "apps");
AddOne(B_APPS_DIRECTORY, "apps");
AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, B_USER_DESKBAR_DIRECTORY,
"Applications", "apps");
AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences");
AddOne(B_PREFERENCES_DIRECTORY, "preferences");
AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, B_USER_DESKBAR_DIRECTORY,
"Preferences", "preferences");
AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", "mail");
AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, "queries", "queries");
AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop");
AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, B_USER_DESKBAR_DIRECTORY,
"Development", "develop");
AddOne(B_USER_CONFIG_DIRECTORY, "config");
AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, "people", "people");
AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, "downloads",
"downloads");
}
WellKnowEntryList *WellKnowEntryList::self = NULL;
}