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