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