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, const BNode* sourceNode, 521 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, uint32 moveMode) 1001 { 1002 ASSERT(!srcList->IsEmpty()); 1003 1004 // extract information from src, dest models 1005 // ## note that we're assuming all items come from the same volume 1006 // ## by looking only at FirstItem here which is not a good idea 1007 dev_t srcVolumeDevice = srcList->FirstItem()->device; 1008 dev_t destVolumeDevice = srcVolumeDevice; 1009 1010 StatStruct deststat; 1011 BVolume volume(srcVolumeDevice); 1012 entry_ref destRef; 1013 1014 bool destIsTrash = false; 1015 BDirectory destDir; 1016 BDirectory* destDirToCheck = NULL; 1017 bool needPreflightNameCheck = false; 1018 bool sourceIsReadOnly = volume.IsReadOnly(); 1019 volume.Unset(); 1020 1021 bool fromUndo = FSIsUndoMoveMode(moveMode); 1022 moveMode = FSMoveMode(moveMode); 1023 1024 // if we're not passed a destEntry then we are supposed to move to trash 1025 if (destEntry != NULL) { 1026 destEntry->GetRef(&destRef); 1027 1028 destDir.SetTo(destEntry); 1029 destDir.GetStat(&deststat); 1030 destDirToCheck = &destDir; 1031 1032 destVolumeDevice = deststat.st_dev; 1033 destIsTrash = FSIsTrashDir(destEntry); 1034 volume.SetTo(destVolumeDevice); 1035 1036 needPreflightNameCheck = true; 1037 } else if (moveMode == kDuplicateSelection) { 1038 BEntry entry; 1039 entry.SetTo(srcList->FirstItem()); 1040 entry.GetParent(&destDir); 1041 volume.SetTo(srcVolumeDevice); 1042 } else { 1043 // move is to trash 1044 destIsTrash = true; 1045 1046 FSGetTrashDir(&destDir, srcVolumeDevice); 1047 volume.SetTo(srcVolumeDevice); 1048 1049 BEntry entry; 1050 destDir.GetEntry(&entry); 1051 destDirToCheck = &destDir; 1052 1053 entry.GetRef(&destRef); 1054 } 1055 1056 // change the move mode if needed 1057 if (moveMode == kCopySelectionTo && destIsTrash) { 1058 // cannot copy to trash 1059 moveMode = kMoveSelectionTo; 1060 } 1061 1062 if (moveMode == kMoveSelectionTo && sourceIsReadOnly) 1063 moveMode = kCopySelectionTo; 1064 1065 bool needSizeCalculation = true; 1066 if ((moveMode == kMoveSelectionTo && srcVolumeDevice == destVolumeDevice) 1067 || destIsTrash) { 1068 needSizeCalculation = false; 1069 } 1070 1071 // we need the undo object later on, so we create it no matter 1072 // if we really need it or not (it's very lightweight) 1073 MoveCopyUndo undo(srcList, destDir, pointList, moveMode); 1074 if (fromUndo) 1075 undo.Remove(); 1076 1077 TrackerCopyLoopControl loopControl; 1078 1079 ConflictCheckResult conflictCheckResult = kPrompt; 1080 int32 collisionCount = 0; 1081 // TODO: Status item is created in InitCopy(), but it would be kind of 1082 // neat to move all that into TrackerCopyLoopControl 1083 status_t result = InitCopy(&loopControl, moveMode, srcList, 1084 &volume, destDirToCheck, &destRef, needPreflightNameCheck, 1085 needSizeCalculation, &collisionCount, &conflictCheckResult); 1086 1087 loopControl.SetSourceList(srcList); 1088 1089 if (result == B_OK) { 1090 for (int32 i = 0; i < srcList->CountItems(); i++) { 1091 BPoint* loc = (BPoint*)-1; 1092 // a loc of -1 forces autoplacement, rather than copying the 1093 // position of the original node 1094 // TODO: 1095 // Clean this mess up! 1096 // What could be a cleaner design is to pass along some kind 1097 // "filter" object that post-processes poses, i.e. adds the 1098 // location or other stuff. It should not be a job of the 1099 // copy-engine. 1100 1101 entry_ref* srcRef = srcList->ItemAt(i); 1102 1103 if (moveMode == kDuplicateSelection) { 1104 BEntry entry(srcRef); 1105 entry.GetParent(&destDir); 1106 destDir.GetStat(&deststat); 1107 volume.SetTo(srcRef->device); 1108 } 1109 1110 // handle case where item is dropped into folder it already lives 1111 // in which could happen if dragging from a query window 1112 if (moveMode != kCreateLink 1113 && moveMode != kCreateRelativeLink 1114 && moveMode != kDuplicateSelection 1115 && !destIsTrash 1116 && (srcRef->device == destRef.device 1117 && srcRef->directory == deststat.st_ino)) { 1118 continue; 1119 } 1120 1121 if (loopControl.CheckUserCanceled()) 1122 break; 1123 1124 BEntry sourceEntry(srcRef); 1125 if (sourceEntry.InitCheck() != B_OK) { 1126 BString error(B_TRANSLATE("Error moving \"%name\".")); 1127 error.ReplaceFirst("%name", srcRef->name); 1128 BAlert* alert = new BAlert("", error.String(), 1129 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1130 B_WARNING_ALERT); 1131 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1132 alert->Go(); 1133 break; 1134 } 1135 1136 // are we moving item to trash? 1137 if (destIsTrash) { 1138 if (pointList != NULL) 1139 loc = (BPoint*)pointList->ItemAt(i); 1140 1141 result = MoveEntryToTrash(&sourceEntry, loc, undo); 1142 if (result != B_OK) { 1143 BString error(B_TRANSLATE("Error moving \"%name\" to Trash. " 1144 "(%error)")); 1145 error.ReplaceFirst("%name", srcRef->name); 1146 error.ReplaceFirst("%error", strerror(result)); 1147 BAlert* alert = new BAlert("", error.String(), 1148 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 1149 B_WARNING_ALERT); 1150 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1151 alert->Go(); 1152 break; 1153 } 1154 continue; 1155 } 1156 1157 // resolve name collisions and hierarchy problems 1158 if (CheckName(moveMode, &sourceEntry, &destDir, 1159 collisionCount > 1, conflictCheckResult) != B_OK) { 1160 // we will skip the current item, because we got a conflict 1161 // and were asked to or because there was some conflict 1162 1163 // update the status because item got skipped and the status 1164 // will not get updated by the move call 1165 loopControl.UpdateStatus(srcRef->name, *srcRef, 1); 1166 1167 continue; 1168 } 1169 1170 // get location to place this item 1171 if (pointList != NULL && moveMode != kCopySelectionTo) { 1172 loc = (BPoint*)pointList->ItemAt(i); 1173 1174 BNode* sourceNode = GetWritableNode(&sourceEntry); 1175 if (sourceNode && sourceNode->InitCheck() == B_OK) { 1176 PoseInfo poseInfo; 1177 poseInfo.fInvisible = false; 1178 poseInfo.fInitedDirectory = deststat.st_ino; 1179 poseInfo.fLocation = *loc; 1180 sourceNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 1181 sizeof(poseInfo)); 1182 } 1183 delete sourceNode; 1184 } 1185 1186 if (pointList != NULL) 1187 loc = (BPoint*)pointList->ItemAt(i); 1188 1189 result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL, undo, &loopControl); 1190 if (result != B_OK) 1191 break; 1192 } 1193 } 1194 1195 // duplicates of srcList, destFolder were created - dispose them 1196 delete srcList; 1197 delete destEntry; 1198 1199 // delete file location list and all Points within 1200 if (pointList != NULL) { 1201 pointList->DoForEach(delete_point); 1202 delete pointList; 1203 } 1204 1205 return B_OK; 1206 } 1207 1208 1209 class FailWithAlert { 1210 public: 1211 static void FailOnError(status_t error, const char* string, 1212 const char* name = NULL) 1213 { 1214 if (error != B_OK) 1215 throw FailWithAlert(error, string, name); 1216 } 1217 1218 FailWithAlert(status_t error, const char* string, const char* name) 1219 : 1220 fString(string), 1221 fName(name), 1222 fError(error) 1223 { 1224 } 1225 1226 const char* fString; 1227 const char* fName; 1228 status_t fError; 1229 }; 1230 1231 1232 class MoveError { 1233 public: 1234 static void FailOnError(status_t error) 1235 { 1236 if (error != B_OK) 1237 throw MoveError(error); 1238 } 1239 1240 MoveError(status_t error) 1241 : 1242 fError(error) 1243 { 1244 } 1245 1246 status_t fError; 1247 }; 1248 1249 1250 void 1251 CopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir, 1252 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName, 1253 Undo &undo) 1254 { 1255 if (loopControl->SkipEntry(srcFile, true)) 1256 return; 1257 1258 node_ref node; 1259 destDir->GetNodeRef(&node); 1260 BVolume volume(node.device); 1261 1262 // check for free space first 1263 if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) { 1264 loopControl->FileError(B_TRANSLATE_NOCOLLECT(kNoFreeSpace), "", 1265 B_DEVICE_FULL, false); 1266 throw (status_t)B_DEVICE_FULL; 1267 } 1268 1269 char destName[B_FILE_NAME_LENGTH]; 1270 srcFile->GetName(destName); 1271 entry_ref ref; 1272 srcFile->GetRef(&ref); 1273 1274 loopControl->UpdateStatus(destName, ref, 1024, true); 1275 1276 if (makeOriginalName) { 1277 BString suffix(" "); 1278 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 1279 FSMakeOriginalName(destName, destDir, suffix.String()); 1280 undo.UpdateEntry(srcFile, destName); 1281 } 1282 1283 BEntry conflictingEntry; 1284 if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) { 1285 switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir, 1286 false, false)) { 1287 case TrackerCopyLoopControl::kSkip: 1288 // we are about to ignore this entire directory 1289 return; 1290 1291 case TrackerCopyLoopControl::kReplace: 1292 if (!conflictingEntry.IsDirectory()) { 1293 ThrowOnError(conflictingEntry.Remove()); 1294 break; 1295 } 1296 // fall through if not a directory 1297 case TrackerCopyLoopControl::kMerge: 1298 // This flag implies that the attributes should be kept 1299 // on the file. Just ignore it. 1300 break; 1301 } 1302 } 1303 1304 try { 1305 LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc); 1306 } catch (status_t err) { 1307 if (err == kCopyCanceled) 1308 throw (status_t)err; 1309 1310 if (err == B_FILE_EXISTS) { 1311 // A file with the same name was created after BDirectory::FindEntry was called and 1312 // before LowLevelCopy could finish. In a move operation, if the standard file error 1313 // alert were displayed and the user chose to continue, the file that we just failed 1314 // to copy would be lost. Don't offer the option to continue. 1315 BString lowLevelExistsString( 1316 B_TRANSLATE("Error copying file \"%name\":\n\t%error\n\n")); 1317 // The error may have resulted from the user dragging a set of selected files to a 1318 // case-insensitive volume, when 2 files in the set differ only in case. 1319 node_ref destRef; 1320 destDir->GetNodeRef(&destRef); 1321 fs_info destInfo; 1322 _kern_read_fs_info(destRef.device, &destInfo); 1323 if (strcmp(destInfo.fsh_name, "fat") == 0) { 1324 lowLevelExistsString += B_TRANSLATE("Note: file names in the destination file " 1325 "system are not case-sensitive.\n"); 1326 } 1327 loopControl->FileError(lowLevelExistsString.String(), destName, err, false); 1328 throw (status_t)err; 1329 } 1330 1331 if (err != B_OK) { 1332 if (!loopControl->FileError( 1333 B_TRANSLATE_NOCOLLECT(kFileErrorString), destName, err, 1334 true)) { 1335 throw (status_t)err; 1336 } else { 1337 // user selected continue in spite of error, update status bar 1338 loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size); 1339 } 1340 } 1341 } 1342 } 1343 1344 1345 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1346 static bool 1347 CreateFileSystemCompatibleName(const BDirectory* destDir, char* destName) 1348 { 1349 // Is it a FAT32 file system? 1350 // (this is the only one we currently know about) 1351 1352 BEntry target; 1353 destDir->GetEntry(&target); 1354 entry_ref targetRef; 1355 fs_info info; 1356 if (target.GetRef(&targetRef) == B_OK 1357 && fs_stat_dev(targetRef.device, &info) == B_OK 1358 && !strcmp(info.fsh_name, "fat")) { 1359 bool wasInvalid = false; 1360 1361 // it's a FAT32 file system, now check the name 1362 1363 int32 length = strlen(destName) - 1; 1364 while (destName[length] == '.') { 1365 // invalid name, just cut off the dot at the end 1366 destName[length--] = '\0'; 1367 wasInvalid = true; 1368 } 1369 1370 char* invalid = destName; 1371 while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) { 1372 invalid[0] = '_'; 1373 wasInvalid = true; 1374 } 1375 1376 return wasInvalid; 1377 } 1378 1379 return false; 1380 } 1381 #endif 1382 1383 1384 static void 1385 LowLevelCopy(BEntry* srcEntry, StatStruct* srcStat, BDirectory* destDir, 1386 char* destName, CopyLoopControl* loopControl, BPoint* loc) 1387 { 1388 entry_ref ref; 1389 ThrowOnError(srcEntry->GetRef(&ref)); 1390 1391 if (S_ISLNK(srcStat->st_mode)) { 1392 // handle symbolic links 1393 BSymLink srcLink; 1394 BSymLink newLink; 1395 char linkpath[MAXPATHLEN]; 1396 1397 ThrowOnError(srcLink.SetTo(srcEntry)); 1398 ssize_t size = srcLink.ReadLink(linkpath, MAXPATHLEN - 1); 1399 if (size < 0) 1400 ThrowOnError(size); 1401 ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink)); 1402 1403 node_ref destNodeRef; 1404 destDir->GetNodeRef(&destNodeRef); 1405 // copy or write new pose location as a first thing 1406 SetupPoseLocation(ref.directory, destNodeRef.node, &srcLink, 1407 &newLink, loc); 1408 1409 BNodeInfo nodeInfo(&newLink); 1410 nodeInfo.SetType(B_LINK_MIMETYPE); 1411 1412 newLink.SetPermissions(srcStat->st_mode); 1413 newLink.SetOwner(srcStat->st_uid); 1414 newLink.SetGroup(srcStat->st_gid); 1415 newLink.SetModificationTime(srcStat->st_mtime); 1416 newLink.SetCreationTime(srcStat->st_crtime); 1417 1418 return; 1419 } 1420 1421 BFile srcFile(srcEntry, O_RDONLY); 1422 ThrowOnInitCheckError(&srcFile); 1423 1424 const size_t kMinBufferSize = 1024* 128; 1425 const size_t kMaxBufferSize = 1024* 1024; 1426 1427 size_t bufsize = kMinBufferSize; 1428 if ((off_t)bufsize < srcStat->st_size) { 1429 // File bigger than the buffer size: determine an optimal buffer size 1430 system_info sinfo; 1431 get_system_info(&sinfo); 1432 size_t freesize = static_cast<size_t>( 1433 (sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE); 1434 bufsize = freesize / 4; 1435 // take 1/4 of RAM max 1436 bufsize -= bufsize % (16* 1024); 1437 // Round to 16 KB boundaries 1438 if (bufsize < kMinBufferSize) { 1439 // at least kMinBufferSize 1440 bufsize = kMinBufferSize; 1441 } else if (bufsize > kMaxBufferSize) { 1442 // no more than kMaxBufferSize 1443 bufsize = kMaxBufferSize; 1444 } 1445 } 1446 1447 BFile destFile(destDir, destName, O_RDWR | O_CREAT); 1448 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1449 if ((destFile.InitCheck() == B_BAD_VALUE 1450 || destFile.InitCheck() == B_NOT_ALLOWED) 1451 && CreateFileSystemCompatibleName(destDir, destName)) { 1452 destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE); 1453 } 1454 #endif 1455 1456 ThrowOnInitCheckError(&destFile); 1457 1458 node_ref destNodeRef; 1459 destDir->GetNodeRef(&destNodeRef); 1460 // copy or write new pose location as a first thing 1461 SetupPoseLocation(ref.directory, destNodeRef.node, &srcFile, 1462 &destFile, loc); 1463 1464 char* buffer = new char[bufsize]; 1465 try { 1466 // copy data portion of file 1467 while (true) { 1468 if (loopControl->CheckUserCanceled()) { 1469 // if copy was canceled, remove partial destination file 1470 destFile.Unset(); 1471 1472 BEntry destEntry; 1473 if (destDir->FindEntry(destName, &destEntry) == B_OK) 1474 destEntry.Remove(); 1475 1476 throw (status_t)kCopyCanceled; 1477 } 1478 1479 ASSERT(buffer); 1480 ssize_t bytes = srcFile.Read(buffer, bufsize); 1481 1482 if (bytes > 0) { 1483 ssize_t updateBytes = 0; 1484 if (bytes > 32* 1024) { 1485 // when copying large chunks, update after read and after 1486 // write to get better update granularity 1487 updateBytes = bytes / 2; 1488 loopControl->UpdateStatus(NULL, ref, updateBytes, true); 1489 } 1490 1491 loopControl->ChecksumChunk(buffer, (size_t)bytes); 1492 1493 ssize_t result = destFile.Write(buffer, (size_t)bytes); 1494 if (result != bytes) { 1495 if (result < 0) 1496 throw (status_t)result; 1497 throw (status_t)B_ERROR; 1498 } 1499 1500 result = destFile.Sync(); 1501 if (result != B_OK) 1502 throw (status_t)result; 1503 1504 loopControl->UpdateStatus(NULL, ref, bytes - updateBytes, 1505 true); 1506 } else if (bytes < 0) { 1507 // read error 1508 throw (status_t)bytes; 1509 } else { 1510 // we are done 1511 break; 1512 } 1513 } 1514 1515 CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize); 1516 } catch (...) { 1517 delete[] buffer; 1518 throw; 1519 } 1520 1521 destFile.SetPermissions(srcStat->st_mode); 1522 destFile.SetOwner(srcStat->st_uid); 1523 destFile.SetGroup(srcStat->st_gid); 1524 destFile.SetModificationTime(srcStat->st_mtime); 1525 destFile.SetCreationTime(srcStat->st_crtime); 1526 1527 delete[] buffer; 1528 1529 if (!loopControl->ChecksumFile(&ref)) { 1530 // File no good. Remove and quit. 1531 destFile.Unset(); 1532 1533 BEntry destEntry; 1534 if (destDir->FindEntry(destName, &destEntry) == B_OK) 1535 destEntry.Remove(); 1536 throw (status_t)kUserCanceled; 1537 } 1538 } 1539 1540 1541 void 1542 CopyAttributes(CopyLoopControl* control, BNode* srcNode, BNode* destNode, 1543 void* buffer, size_t bufsize) 1544 { 1545 // ToDo: 1546 // Add error checking 1547 // prior to coyping attributes, make sure indices are installed 1548 1549 // When calling CopyAttributes on files, have to make sure destNode 1550 // is a BFile opened R/W 1551 1552 srcNode->RewindAttrs(); 1553 char name[256]; 1554 while (srcNode->GetNextAttrName(name) == B_OK) { 1555 // Check to see if this attribute should be skipped. 1556 if (control->SkipAttribute(name)) 1557 continue; 1558 1559 attr_info info; 1560 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1561 continue; 1562 1563 // Check to see if this attribute should be overwritten when it 1564 // already exists. 1565 if (control->PreserveAttribute(name)) { 1566 attr_info dest_info; 1567 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1568 continue; 1569 } 1570 1571 // Special case for a size 0 attribute. It wouldn't be written at all 1572 // otherwise. 1573 if (info.size == 0) 1574 destNode->WriteAttr(name, info.type, 0, buffer, 0); 1575 1576 ssize_t bytes; 1577 ssize_t numToRead = (ssize_t)info.size; 1578 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1579 size_t chunkSize = (size_t)numToRead; 1580 if (chunkSize > bufsize) 1581 chunkSize = bufsize; 1582 1583 bytes = srcNode->ReadAttr(name, info.type, offset, 1584 buffer, chunkSize); 1585 1586 if (bytes <= 0) 1587 break; 1588 1589 destNode->WriteAttr(name, info.type, offset, buffer, 1590 (size_t)bytes); 1591 1592 numToRead -= bytes; 1593 } 1594 } 1595 } 1596 1597 1598 static void 1599 CopyFolder(BEntry* srcEntry, BDirectory* destDir, 1600 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName, 1601 Undo &undo, bool removeSource = false) 1602 { 1603 BDirectory newDir; 1604 BEntry entry; 1605 status_t err = B_OK; 1606 bool createDirectory = true; 1607 BEntry existingEntry; 1608 1609 if (loopControl->SkipEntry(srcEntry, false)) 1610 return; 1611 1612 entry_ref ref; 1613 srcEntry->GetRef(&ref); 1614 1615 char destName[B_FILE_NAME_LENGTH]; 1616 strlcpy(destName, ref.name, sizeof(destName)); 1617 1618 loopControl->UpdateStatus(ref.name, ref, 1024, true); 1619 1620 if (makeOriginalName) { 1621 BString suffix(" "); 1622 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 1623 FSMakeOriginalName(destName, destDir, suffix.String()); 1624 undo.UpdateEntry(srcEntry, destName); 1625 } 1626 1627 if (destDir->FindEntry(destName, &existingEntry) == B_OK) { 1628 // some entry with a conflicting name is already present in destDir 1629 // decide what to do about it 1630 bool isDirectory = existingEntry.IsDirectory(); 1631 1632 switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir, 1633 true, isDirectory)) { 1634 case TrackerCopyLoopControl::kSkip: 1635 // we are about to ignore this entire directory 1636 return; 1637 1638 1639 case TrackerCopyLoopControl::kReplace: 1640 if (!isDirectory) { 1641 // conflicting with a file or symbolic link, remove entry 1642 ThrowOnError(existingEntry.Remove()); 1643 break; 1644 } 1645 // fall through if directory, do not replace. 1646 case TrackerCopyLoopControl::kMerge: 1647 ASSERT(isDirectory); 1648 // do not create a new directory, use the current one 1649 newDir.SetTo(&existingEntry); 1650 createDirectory = false; 1651 break; 1652 } 1653 } 1654 1655 // loop through everything in src folder and copy it to new folder 1656 BDirectory srcDir(srcEntry); 1657 srcDir.Rewind(); 1658 1659 // create a new folder inside of destination folder 1660 if (createDirectory) { 1661 err = destDir->CreateDirectory(destName, &newDir); 1662 #ifdef _SILENTLY_CORRECT_FILE_NAMES 1663 if (err == B_BAD_VALUE) { 1664 // check if it's an invalid name on a FAT32 file system 1665 if (CreateFileSystemCompatibleName(destDir, destName)) 1666 err = destDir->CreateDirectory(destName, &newDir); 1667 } 1668 #endif 1669 if (err != B_OK) { 1670 if (!loopControl->FileError(B_TRANSLATE_NOCOLLECT( 1671 kFolderErrorString), destName, err, true)) { 1672 throw err; 1673 } 1674 1675 // will allow rest of copy to continue 1676 return; 1677 } 1678 } 1679 1680 char* buffer; 1681 if (createDirectory && err == B_OK 1682 && (buffer = (char*)malloc(32768)) != 0) { 1683 CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768); 1684 // don't copy original pose location if new location passed 1685 free(buffer); 1686 } 1687 1688 StatStruct statbuf; 1689 srcDir.GetStat(&statbuf); 1690 dev_t sourceDeviceID = statbuf.st_dev; 1691 1692 // copy or write new pose location 1693 node_ref destNodeRef; 1694 destDir->GetNodeRef(&destNodeRef); 1695 SetupPoseLocation(ref.directory, destNodeRef.node, &srcDir, 1696 &newDir, loc); 1697 1698 while (srcDir.GetNextEntry(&entry) == B_OK) { 1699 1700 if (loopControl->CheckUserCanceled()) 1701 throw (status_t)kUserCanceled; 1702 1703 entry.GetStat(&statbuf); 1704 1705 if (S_ISDIR(statbuf.st_mode)) { 1706 1707 // entry is a mount point, do not copy it 1708 if (statbuf.st_dev != sourceDeviceID) { 1709 PRINT(("Avoiding mount point %" B_PRIdDEV ", %" B_PRIdDEV "\n", 1710 statbuf.st_dev, sourceDeviceID)); 1711 continue; 1712 } 1713 1714 CopyFolder(&entry, &newDir, loopControl, 0, false, undo, 1715 removeSource); 1716 if (removeSource) 1717 FSDeleteFolder(&entry, loopControl, true, true, false); 1718 } else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) { 1719 CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo); 1720 if (removeSource) 1721 entry.Remove(); 1722 } else { 1723 // Ignore special files 1724 } 1725 } 1726 if (removeSource) 1727 srcEntry->Remove(); 1728 else 1729 srcEntry->Unset(); 1730 } 1731 1732 1733 status_t 1734 RecursiveMove(BEntry* entry, BDirectory* destDir, CopyLoopControl* loopControl) 1735 { 1736 const char* name = entry->Name(); 1737 1738 if (destDir->Contains(name)) { 1739 BPath path (destDir, name); 1740 BDirectory subDir (path.Path()); 1741 entry_ref ref; 1742 entry->GetRef(&ref); 1743 BDirectory source(&ref); 1744 if (source.InitCheck() == B_OK) { 1745 source.Rewind(); 1746 BEntry current; 1747 while (source.GetNextEntry(¤t) == B_OK) { 1748 if (current.IsDirectory()) { 1749 RecursiveMove(¤t, &subDir, loopControl); 1750 current.Remove(); 1751 } else { 1752 name = current.Name(); 1753 if (loopControl->OverwriteOnConflict(¤t, name, 1754 &subDir, true, false) 1755 != TrackerCopyLoopControl::kSkip) { 1756 MoveError::FailOnError(current.MoveTo(&subDir, 1757 NULL, true)); 1758 } 1759 } 1760 } 1761 } 1762 entry->Remove(); 1763 } else 1764 MoveError::FailOnError(entry->MoveTo(destDir)); 1765 1766 return B_OK; 1767 } 1768 1769 status_t 1770 MoveItem(BEntry* entry, BDirectory* destDir, BPoint* loc, uint32 moveMode, 1771 const char* newName, Undo &undo, CopyLoopControl* loopControl) 1772 { 1773 entry_ref ref; 1774 try { 1775 node_ref destNode; 1776 StatStruct statbuf; 1777 MoveError::FailOnError(entry->GetStat(&statbuf)); 1778 MoveError::FailOnError(entry->GetRef(&ref)); 1779 MoveError::FailOnError(destDir->GetNodeRef(&destNode)); 1780 1781 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 1782 PoseInfo poseInfo; 1783 char name[B_FILE_NAME_LENGTH]; 1784 strlcpy(name, ref.name, sizeof(name)); 1785 1786 BSymLink link; 1787 BString suffix(" "); 1788 suffix << B_TRANSLATE_COMMENT("link", "filename link"), 1789 FSMakeOriginalName(name, destDir, suffix.String()); 1790 undo.UpdateEntry(entry, name); 1791 1792 BPath path; 1793 entry->GetPath(&path); 1794 if (loc && loc != (BPoint*)-1) { 1795 poseInfo.fInvisible = false; 1796 poseInfo.fInitedDirectory = destNode.node; 1797 poseInfo.fLocation = *loc; 1798 } 1799 1800 status_t err = B_ERROR; 1801 1802 if (moveMode == kCreateRelativeLink) { 1803 if (statbuf.st_dev == destNode.device) { 1804 // relative link only works on the same device 1805 char oldwd[B_PATH_NAME_LENGTH]; 1806 getcwd(oldwd, B_PATH_NAME_LENGTH); 1807 1808 BEntry destEntry; 1809 destDir->GetEntry(&destEntry); 1810 BPath destPath; 1811 destEntry.GetPath(&destPath); 1812 1813 chdir(destPath.Path()); 1814 // change working dir to target dir 1815 1816 BString destString(destPath.Path()); 1817 destString.Append("/"); 1818 1819 BString srcString(path.Path()); 1820 srcString.RemoveLast(path.Leaf()); 1821 1822 // find index while paths are the same 1823 1824 const char* src = srcString.String(); 1825 const char* dest = destString.String(); 1826 const char* lastFolderSrc = src; 1827 const char* lastFolderDest = dest; 1828 1829 while (*src && *dest && *src == *dest) { 1830 ++src; 1831 if (*dest++ == '/') { 1832 lastFolderSrc = src; 1833 lastFolderDest = dest; 1834 } 1835 } 1836 src = lastFolderSrc; 1837 dest = lastFolderDest; 1838 1839 BString source; 1840 if (*dest == '\0' && *src != '\0') { 1841 // source is deeper in the same tree than the target 1842 source.Append(src); 1843 } else if (*dest != '\0') { 1844 // target is deeper in the same tree than the source 1845 while (*dest) { 1846 if (*dest == '/') 1847 source.Prepend("../"); 1848 ++dest; 1849 } 1850 source.Append(src); 1851 } 1852 1853 // else source and target are in the same dir 1854 1855 source.Append(path.Leaf()); 1856 err = destDir->CreateSymLink(name, source.String(), 1857 &link); 1858 1859 chdir(oldwd); 1860 // change working dir back to original 1861 } else 1862 moveMode = kCreateLink; 1863 // fall back to absolute link mode 1864 } 1865 1866 if (moveMode == kCreateLink) 1867 err = destDir->CreateSymLink(name, path.Path(), &link); 1868 1869 if (err == B_UNSUPPORTED) { 1870 throw FailWithAlert(err, 1871 B_TRANSLATE("The target disk does not support " 1872 "creating links."), NULL); 1873 } 1874 1875 FailWithAlert::FailOnError(err, 1876 B_TRANSLATE("Error creating link to \"%name\"."), 1877 ref.name); 1878 1879 if (loc && loc != (BPoint*)-1) { 1880 link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 1881 sizeof(PoseInfo)); 1882 } 1883 1884 BNodeInfo nodeInfo(&link); 1885 nodeInfo.SetType(B_LINK_MIMETYPE); 1886 return B_OK; 1887 } 1888 1889 // if move is on same volume don't copy 1890 if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo 1891 && moveMode != kDuplicateSelection) { 1892 // for "Move" the size for status is always 1 - since file 1893 // size is irrelevant when simply moving to a new folder 1894 loopControl->UpdateStatus(ref.name, ref, 1); 1895 if (entry->IsDirectory()) 1896 return RecursiveMove(entry, destDir, loopControl); 1897 1898 MoveError::FailOnError(entry->MoveTo(destDir, newName)); 1899 } else { 1900 bool makeOriginalName = (moveMode == kDuplicateSelection); 1901 if (S_ISDIR(statbuf.st_mode)) { 1902 CopyFolder(entry, destDir, loopControl, loc, makeOriginalName, 1903 undo, moveMode == kMoveSelectionTo); 1904 } else { 1905 CopyFile(entry, &statbuf, destDir, loopControl, loc, 1906 makeOriginalName, undo); 1907 if (moveMode == kMoveSelectionTo) 1908 entry->Remove(); 1909 } 1910 } 1911 } catch (status_t error) { 1912 // no alert, was already taken care of before 1913 return error; 1914 } catch (MoveError& error) { 1915 BString errorString(B_TRANSLATE("Error moving \"%name\"")); 1916 errorString.ReplaceFirst("%name", ref.name); 1917 BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"), 1918 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1919 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1920 alert->Go(); 1921 return error.fError; 1922 } catch (FailWithAlert& error) { 1923 BString buffer(error.fString); 1924 if (error.fName != NULL) 1925 buffer.ReplaceFirst("%name", error.fName); 1926 else 1927 buffer << error.fString; 1928 1929 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("OK"), 1930 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1931 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1932 alert->Go(); 1933 1934 return error.fError; 1935 } 1936 1937 return B_OK; 1938 } 1939 1940 1941 void 1942 FSDuplicate(BObjectList<entry_ref>* srcList, BList* pointList) 1943 { 1944 LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList, 1945 (BEntry*)NULL, pointList, kDuplicateSelection); 1946 } 1947 1948 1949 #if 0 1950 status_t 1951 FSCopyFolder(BEntry* srcEntry, BDirectory* destDir, 1952 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName) 1953 { 1954 try 1955 CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName); 1956 catch (status_t error) { 1957 return error; 1958 1959 return B_OK; 1960 } 1961 #endif 1962 1963 1964 status_t 1965 FSCopyAttributesAndStats(BNode* srcNode, BNode* destNode, bool copyTimes) 1966 { 1967 char* buffer = new char[1024]; 1968 1969 // copy the attributes 1970 srcNode->RewindAttrs(); 1971 char name[256]; 1972 while (srcNode->GetNextAttrName(name) == B_OK) { 1973 attr_info info; 1974 if (srcNode->GetAttrInfo(name, &info) != B_OK) 1975 continue; 1976 1977 attr_info dest_info; 1978 if (destNode->GetAttrInfo(name, &dest_info) == B_OK) 1979 continue; 1980 1981 ssize_t bytes; 1982 ssize_t numToRead = (ssize_t)info.size; 1983 for (off_t offset = 0; numToRead > 0; offset += bytes) { 1984 size_t chunkSize = (size_t)numToRead; 1985 if (chunkSize > 1024) 1986 chunkSize = 1024; 1987 1988 bytes = srcNode->ReadAttr(name, info.type, offset, buffer, 1989 chunkSize); 1990 1991 if (bytes <= 0) 1992 break; 1993 1994 destNode->WriteAttr(name, info.type, offset, buffer, 1995 (size_t)bytes); 1996 1997 numToRead -= bytes; 1998 } 1999 } 2000 delete[] buffer; 2001 2002 // copy the file stats 2003 struct stat srcStat; 2004 srcNode->GetStat(&srcStat); 2005 destNode->SetPermissions(srcStat.st_mode); 2006 destNode->SetOwner(srcStat.st_uid); 2007 destNode->SetGroup(srcStat.st_gid); 2008 if (copyTimes) { 2009 destNode->SetModificationTime(srcStat.st_mtime); 2010 destNode->SetCreationTime(srcStat.st_crtime); 2011 } 2012 2013 return B_OK; 2014 } 2015 2016 2017 #if 0 2018 status_t 2019 FSCopyFile(BEntry* srcFile, StatStruct* srcStat, BDirectory* destDir, 2020 CopyLoopControl* loopControl, BPoint* loc, bool makeOriginalName) 2021 { 2022 try { 2023 CopyFile(srcFile, srcStat, destDir, loopControl, loc, 2024 makeOriginalName); 2025 } catch (status_t error) { 2026 return error; 2027 } 2028 2029 return B_OK; 2030 } 2031 #endif 2032 2033 2034 static status_t 2035 MoveEntryToTrash(BEntry* entry, BPoint* loc, Undo &undo) 2036 { 2037 BDirectory trash_dir; 2038 entry_ref ref; 2039 status_t result = entry->GetRef(&ref); 2040 if (result != B_OK) 2041 return result; 2042 2043 node_ref nodeRef; 2044 result = entry->GetNodeRef(&nodeRef); 2045 if (result != B_OK) 2046 return result; 2047 2048 StatStruct statbuf; 2049 result = entry->GetStat(&statbuf); 2050 if (entry->GetStat(&statbuf) != B_OK) 2051 return result; 2052 2053 // if it's a directory close the window and any child dir windows 2054 if (S_ISDIR(statbuf.st_mode)) { 2055 BDirectory dir(entry); 2056 2057 // if it's a volume, try to unmount 2058 if (dir.IsRootDirectory()) { 2059 BVolume volume(nodeRef.device); 2060 BVolume boot; 2061 2062 BVolumeRoster().GetBootVolume(&boot); 2063 if (volume == boot) { 2064 char name[B_FILE_NAME_LENGTH]; 2065 volume.GetName(name); 2066 BString buffer( 2067 B_TRANSLATE("Cannot unmount the boot volume \"%name\".")); 2068 buffer.ReplaceFirst("%name", name); 2069 BAlert* alert = new BAlert("", buffer.String(), 2070 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 2071 B_WARNING_ALERT); 2072 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2073 alert->Go(); 2074 } else { 2075 BMessage message(kUnmountVolume); 2076 message.AddInt32("device_id", volume.Device()); 2077 be_app->PostMessage(&message); 2078 } 2079 return B_OK; 2080 } 2081 2082 // get trash directory on same volume as item being moved 2083 result = FSGetTrashDir(&trash_dir, nodeRef.device); 2084 if (result != B_OK) 2085 return result; 2086 2087 // check hierarchy before moving 2088 BEntry trashEntry; 2089 trash_dir.GetEntry(&trashEntry); 2090 2091 if (dir == trash_dir || dir.Contains(&trashEntry)) { 2092 BAlert* alert = new BAlert("", 2093 B_TRANSLATE("You cannot put the selected item(s) " 2094 "into the trash."), 2095 B_TRANSLATE("OK"), 2096 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2097 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2098 alert->Go(); 2099 2100 // return no error so we don't get two dialogs 2101 return B_OK; 2102 } 2103 2104 BMessage message(kCloseWindowAndChildren); 2105 2106 node_ref parentNode; 2107 parentNode.device = statbuf.st_dev; 2108 parentNode.node = statbuf.st_ino; 2109 message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref)); 2110 be_app->PostMessage(&message); 2111 } else { 2112 // get trash directory on same volume as item being moved 2113 result = FSGetTrashDir(&trash_dir, nodeRef.device); 2114 if (result != B_OK) 2115 return result; 2116 } 2117 2118 // make sure name doesn't conflict with anything in trash already 2119 char name[B_FILE_NAME_LENGTH]; 2120 strlcpy(name, ref.name, sizeof(name)); 2121 if (trash_dir.Contains(name)) { 2122 BString suffix(" "); 2123 suffix << B_TRANSLATE_COMMENT("copy", "filename copy"), 2124 FSMakeOriginalName(name, &trash_dir, suffix.String()); 2125 undo.UpdateEntry(entry, name); 2126 } 2127 2128 BNode* sourceNode = 0; 2129 if (loc && loc != (BPoint*)-1 && (sourceNode = GetWritableNode(entry, &statbuf)) != 0) { 2130 trash_dir.GetStat(&statbuf); 2131 PoseInfo poseInfo; 2132 poseInfo.fInvisible = false; 2133 poseInfo.fInitedDirectory = statbuf.st_ino; 2134 poseInfo.fLocation = *loc; 2135 sourceNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo)); 2136 delete sourceNode; 2137 } 2138 2139 BNode node(entry); 2140 BPath path; 2141 // Get path of entry before it's moved to the trash 2142 // and write it to the file as an attribute 2143 if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) { 2144 BString originalPath(path.Path()); 2145 node.WriteAttrString(kAttrOriginalPath, &originalPath); 2146 } 2147 2148 TrackerCopyLoopControl loopControl; 2149 MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo, &loopControl); 2150 2151 return B_OK; 2152 } 2153 2154 2155 ConflictCheckResult 2156 PreFlightNameCheck(BObjectList<entry_ref>* srcList, const BDirectory* destDir, 2157 int32* collisionCount, uint32 moveMode) 2158 { 2159 // count the number of name collisions in dest folder 2160 *collisionCount = 0; 2161 2162 int32 count = srcList->CountItems(); 2163 for (int32 i = 0; i < count; i++) { 2164 entry_ref* srcRef = srcList->ItemAt(i); 2165 BEntry entry(srcRef); 2166 BDirectory parent; 2167 entry.GetParent(&parent); 2168 2169 if (parent != *destDir && destDir->Contains(srcRef->name)) 2170 (*collisionCount)++; 2171 } 2172 2173 // prompt user only if there is more than one collision, otherwise the 2174 // single collision case will be handled as a "Prompt" case by CheckName 2175 if (*collisionCount > 1) { 2176 const char* verb = (moveMode == kMoveSelectionTo) 2177 ? B_TRANSLATE("moving") : B_TRANSLATE("copying"); 2178 BString replaceMsg(B_TRANSLATE_NOCOLLECT(kReplaceManyStr)); 2179 replaceMsg.ReplaceAll("%verb", verb); 2180 2181 BAlert* alert = new BAlert(); 2182 alert->SetText(replaceMsg.String()); 2183 alert->AddButton(B_TRANSLATE("Cancel")); 2184 alert->AddButton(B_TRANSLATE("Prompt")); 2185 alert->AddButton(B_TRANSLATE("Skip all")); 2186 alert->AddButton(B_TRANSLATE("Replace all")); 2187 alert->SetShortcut(0, B_ESCAPE); 2188 switch (alert->Go()) { 2189 case 0: 2190 return kCanceled; 2191 2192 case 1: 2193 // user selected "Prompt" 2194 return kPrompt; 2195 2196 case 2: 2197 // user selected "Skip all" 2198 return kSkipAll; 2199 2200 case 3: 2201 // user selected "Replace all" 2202 return kReplaceAll; 2203 } 2204 } 2205 2206 return kNoConflicts; 2207 } 2208 2209 2210 void 2211 FileStatToString(StatStruct* stat, char* buffer, int32 length) 2212 { 2213 tm timeData; 2214 localtime_r(&stat->st_mtime, &timeData); 2215 2216 BString size; 2217 static BStringFormat format( 2218 B_TRANSLATE("{0, plural, one{# byte} other{# bytes}}")); 2219 format.Format(size, stat->st_size); 2220 uint32 pos = snprintf(buffer, length, "\n\t(%s ", size.String()); 2221 2222 strftime(buffer + pos, length - pos, "%b %d %Y, %I:%M:%S %p)", &timeData); 2223 } 2224 2225 2226 status_t 2227 CheckName(uint32 moveMode, const BEntry* sourceEntry, 2228 const BDirectory* destDir, bool multipleCollisions, 2229 ConflictCheckResult& conflictResolution) 2230 { 2231 if (moveMode == kDuplicateSelection) { 2232 // when duplicating, we will never have a conflict 2233 return B_OK; 2234 } 2235 2236 // see if item already exists in destination dir 2237 const char* name = sourceEntry->Name(); 2238 bool sourceIsDirectory = sourceEntry->IsDirectory(); 2239 2240 BDirectory srcDirectory; 2241 if (sourceIsDirectory) { 2242 srcDirectory.SetTo(sourceEntry); 2243 BEntry destEntry; 2244 destDir->GetEntry(&destEntry); 2245 2246 if (moveMode != kCreateLink && moveMode != kCreateRelativeLink 2247 && (srcDirectory == *destDir 2248 || srcDirectory.Contains(&destEntry))) { 2249 BAlert* alert = new BAlert("", 2250 B_TRANSLATE("You can't move a folder into itself " 2251 "or any of its own sub-folders."), B_TRANSLATE("OK"), 2252 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2253 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2254 alert->Go(); 2255 return B_ERROR; 2256 } 2257 } 2258 2259 if (FSIsTrashDir(sourceEntry) && moveMode != kCreateLink 2260 && moveMode != kCreateRelativeLink) { 2261 BAlert* alert = new BAlert("", 2262 B_TRANSLATE("You can't move or copy the trash."), 2263 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, 2264 B_WARNING_ALERT); 2265 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2266 alert->Go(); 2267 return B_ERROR; 2268 } 2269 2270 BEntry entry; 2271 if (destDir->FindEntry(name, &entry) != B_OK) { 2272 // no conflict, return 2273 return B_OK; 2274 } 2275 2276 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 2277 // if we are creating link in the same directory, the conflict will 2278 // be handled later by giving the link a unique name 2279 sourceEntry->GetParent(&srcDirectory); 2280 2281 if (srcDirectory == *destDir) 2282 return B_OK; 2283 } 2284 2285 bool destIsDir = entry.IsDirectory(); 2286 // be sure not to replace the parent directory of the item being moved 2287 if (destIsDir) { 2288 BDirectory targetDir(&entry); 2289 if (targetDir.Contains(sourceEntry)) { 2290 BAlert* alert = new BAlert("", 2291 B_TRANSLATE("You can't replace a folder " 2292 "with one of its sub-folders."), 2293 B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2294 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2295 alert->Go(); 2296 return B_ERROR; 2297 } 2298 } 2299 2300 // ensure that the user isn't trying to replace a file with folder 2301 // or vice-versa 2302 if (moveMode != kCreateLink 2303 && moveMode != kCreateRelativeLink 2304 && destIsDir != sourceIsDirectory) { 2305 BAlert* alert = new BAlert("", sourceIsDirectory 2306 ? B_TRANSLATE("You cannot replace a file with a folder or a " 2307 "symbolic link.") 2308 : B_TRANSLATE("You cannot replace a folder or a symbolic link " 2309 "with a file."), B_TRANSLATE("OK"), 0, 0, B_WIDTH_AS_USUAL, 2310 B_WARNING_ALERT); 2311 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2312 alert->Go(); 2313 return B_ERROR; 2314 } 2315 2316 if (conflictResolution == kSkipAll) 2317 return B_ERROR; 2318 2319 if (conflictResolution != kReplaceAll) { 2320 // prompt user to determine whether to replace or not 2321 BString replaceMsg; 2322 2323 if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { 2324 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kSymLinkReplaceStr)); 2325 replaceMsg.ReplaceFirst("%name", name); 2326 } else if (sourceEntry->IsDirectory()) { 2327 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kDirectoryReplaceStr)); 2328 replaceMsg.ReplaceFirst("%name", name); 2329 replaceMsg.ReplaceFirst("%verb", 2330 moveMode == kMoveSelectionTo 2331 ? B_TRANSLATE("moving") 2332 : B_TRANSLATE("copying")); 2333 } else { 2334 char sourceBuffer[96], destBuffer[96]; 2335 StatStruct statBuffer; 2336 2337 if (!sourceEntry->IsDirectory() 2338 && sourceEntry->GetStat(&statBuffer) == B_OK) { 2339 FileStatToString(&statBuffer, sourceBuffer, 96); 2340 } else 2341 sourceBuffer[0] = '\0'; 2342 2343 if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK) 2344 FileStatToString(&statBuffer, destBuffer, 96); 2345 else 2346 destBuffer[0] = '\0'; 2347 2348 replaceMsg.SetTo(B_TRANSLATE_NOCOLLECT(kReplaceStr)); 2349 replaceMsg.ReplaceAll("%name", name); 2350 replaceMsg.ReplaceFirst("%dest", destBuffer); 2351 replaceMsg.ReplaceFirst("%src", sourceBuffer); 2352 replaceMsg.ReplaceFirst("%movemode", moveMode == kMoveSelectionTo 2353 ? B_TRANSLATE("moving") : B_TRANSLATE("copying")); 2354 } 2355 2356 // special case single collision (don't need Replace All shortcut) 2357 BAlert* alert; 2358 if (multipleCollisions || sourceIsDirectory) { 2359 alert = new BAlert(); 2360 alert->SetText(replaceMsg.String()); 2361 alert->AddButton(B_TRANSLATE("Skip")); 2362 alert->AddButton(B_TRANSLATE("Skip all")); 2363 alert->AddButton(B_TRANSLATE("Replace")); 2364 alert->AddButton(B_TRANSLATE("Replace all")); 2365 switch (alert->Go()) { 2366 case 0: 2367 conflictResolution = kCanceled; 2368 return B_ERROR; 2369 case 1: 2370 conflictResolution = kSkipAll; 2371 return B_ERROR; 2372 case 2: 2373 conflictResolution = kReplace; 2374 break; 2375 case 3: 2376 conflictResolution = kReplaceAll; 2377 break; 2378 } 2379 } else { 2380 alert = new BAlert("", replaceMsg.String(), 2381 B_TRANSLATE("Cancel"), B_TRANSLATE("Replace")); 2382 alert->SetShortcut(0, B_ESCAPE); 2383 switch (alert->Go()) { 2384 case 0: 2385 conflictResolution = kCanceled; 2386 return B_ERROR; 2387 case 1: 2388 conflictResolution = kReplace; 2389 break; 2390 } 2391 } 2392 } 2393 2394 // delete destination item 2395 if (destIsDir) 2396 return B_OK; 2397 2398 status_t status = entry.Remove(); 2399 if (status != B_OK) { 2400 BString error(B_TRANSLATE("There was a problem trying to replace " 2401 "\"%name\". The item might be open or busy.")); 2402 error.ReplaceFirst("%name", name); 2403 BAlert* alert = new BAlert("", error.String(), 2404 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2405 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2406 alert->Go(); 2407 } 2408 2409 return status; 2410 } 2411 2412 2413 status_t 2414 FSDeleteFolder(BEntry* dirEntry, CopyLoopControl* loopControl, 2415 bool updateStatus, bool deleteTopDir, bool upateFileNameInStatus) 2416 { 2417 BDirectory dir(dirEntry); 2418 2419 // loop through everything in folder and delete it, skipping trouble files 2420 BEntry entry; 2421 while (dir.GetNextEntry(&entry) == B_OK) { 2422 entry_ref ref; 2423 entry.GetRef(&ref); 2424 2425 if (loopControl->CheckUserCanceled()) 2426 return kTrashCanceled; 2427 2428 status_t status; 2429 2430 if (entry.IsDirectory()) 2431 status = FSDeleteFolder(&entry, loopControl, updateStatus, true, 2432 upateFileNameInStatus); 2433 else { 2434 status = entry.Remove(); 2435 if (updateStatus) { 2436 loopControl->UpdateStatus(upateFileNameInStatus ? ref.name 2437 : "", ref, 1, true); 2438 } 2439 } 2440 2441 if (status == kTrashCanceled) 2442 return kTrashCanceled; 2443 2444 if (status != B_OK) { 2445 loopControl->FileError(B_TRANSLATE_NOCOLLECT( 2446 kFileDeleteErrorString), ref.name, status, false); 2447 } 2448 } 2449 2450 if (loopControl->CheckUserCanceled()) 2451 return kTrashCanceled; 2452 2453 entry_ref ref; 2454 dirEntry->GetRef(&ref); 2455 2456 if (updateStatus && deleteTopDir) 2457 loopControl->UpdateStatus(NULL, ref, 1); 2458 2459 if (deleteTopDir) 2460 return dirEntry->Remove(); 2461 2462 return B_OK; 2463 } 2464 2465 2466 void 2467 FSMakeOriginalName(BString &string, const BDirectory* destDir, 2468 const char* suffix) 2469 { 2470 if (!destDir->Contains(string.String())) 2471 return; 2472 2473 FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH), 2474 const_cast<BDirectory*>(destDir), suffix ? suffix : NULL); 2475 string.UnlockBuffer(); 2476 } 2477 2478 2479 void 2480 FSMakeOriginalName(char* name, BDirectory* destDir, const char* suffix) 2481 { 2482 char root[B_FILE_NAME_LENGTH]; 2483 char copybase[B_FILE_NAME_LENGTH]; 2484 char tempName[B_FILE_NAME_LENGTH + 11]; 2485 int32 fnum; 2486 2487 // is this name already original? 2488 if (!destDir->Contains(name)) 2489 return; 2490 2491 BString copySuffix(B_TRANSLATE_COMMENT("copy", "filename copy")); 2492 size_t suffixLength = copySuffix.Length(); 2493 if (suffix == NULL) 2494 suffix = copySuffix; 2495 2496 // Determine if we're copying a 'copy'. This algorithm isn't perfect. 2497 // If you're copying a file whose REAL name ends with 'copy' then 2498 // this method will return "<filename> 1", not "<filename> copy" 2499 2500 // However, it will correctly handle file that contain 'copy' 2501 // elsewhere in their name. 2502 2503 bool copycopy = false; // are we copying a copy? 2504 int32 len = (int32)strlen(name); 2505 char* p = name + len - 1; // get pointer to end of name 2506 2507 // eat up optional numbers (if we're copying "<filename> copy 34") 2508 while ((p > name) && isdigit(*p)) 2509 p--; 2510 2511 // eat up optional spaces 2512 while ((p > name) && isspace(*p)) 2513 p--; 2514 2515 // now look for the phrase " copy" 2516 if (p > name) { 2517 // p points to the last char of the word. For example, 'y' in 'copy' 2518 2519 if ((p - suffixLength > name) 2520 && (strncmp(p - suffixLength, suffix, suffixLength + 1) == 0)) { 2521 // we found 'copy' in the right place. 2522 // so truncate after 'copy' 2523 *(p + 1) = '\0'; 2524 copycopy = true; 2525 2526 // save the 'root' name of the file, for possible later use. 2527 // that is copy everything but trailing " copy". Need to 2528 // NULL terminate after copy 2529 strncpy(root, name, (uint32)((p - name) - suffixLength)); 2530 root[(p - name) - suffixLength] = '\0'; 2531 } 2532 } 2533 2534 if (!copycopy) { 2535 // The name can't be longer than B_FILE_NAME_LENGTH. 2536 // The algorithm adds a localized " copy XX" to the name. 2537 // That's the number of characters of "copy" + 4 (spaces + "XX"). 2538 // B_FILE_NAME_LENGTH already accounts for NULL termination so we 2539 // don't need to save an extra char at the end. 2540 if (strlen(name) > B_FILE_NAME_LENGTH - (suffixLength + 4)) { 2541 // name is too long - truncate it! 2542 name[B_FILE_NAME_LENGTH - (suffixLength + 4)] = '\0'; 2543 } 2544 2545 strlcpy(root, name, sizeof(root)); 2546 // save root name 2547 strlcat(name, suffix, B_FILE_NAME_LENGTH); 2548 } 2549 2550 strlcpy(copybase, name, sizeof(copybase)); 2551 2552 // if name already exists then add a number 2553 fnum = 1; 2554 strlcpy(tempName, name, sizeof(tempName)); 2555 while (destDir->Contains(tempName)) { 2556 snprintf(tempName, sizeof(tempName), "%s %" B_PRId32, copybase, 2557 ++fnum); 2558 2559 if (strlen(tempName) > (B_FILE_NAME_LENGTH - 1)) { 2560 // The name has grown too long. Maybe we just went from 2561 // "<filename> copy 9" to "<filename> copy 10" and that extra 2562 // character was too much. The solution is to further 2563 // truncate the 'root' name and continue. 2564 // ??? should we reset fnum or not ??? 2565 root[strlen(root) - 1] = '\0'; 2566 snprintf(tempName, sizeof(tempName), "%s%s %" B_PRId32, root, 2567 suffix, fnum); 2568 } 2569 } 2570 2571 strlcpy(name, tempName, B_FILE_NAME_LENGTH); 2572 } 2573 2574 2575 status_t 2576 FSRecursiveCalcSize(BInfoWindow* window, CopyLoopControl* loopControl, 2577 BDirectory* dir, off_t* _runningSize, int32* _fileCount, int32* _dirCount) 2578 { 2579 dir->Rewind(); 2580 BEntry entry; 2581 while (dir->GetNextEntry(&entry) == B_OK) { 2582 // be sure window hasn't closed 2583 if (window && window->StopCalc()) 2584 return B_OK; 2585 2586 if (loopControl->CheckUserCanceled()) 2587 return kUserCanceled; 2588 2589 StatStruct statbuf; 2590 status_t status = entry.GetStat(&statbuf); 2591 if (status != B_OK) 2592 return status; 2593 2594 (*_runningSize) += statbuf.st_blocks * 512; 2595 2596 if (S_ISDIR(statbuf.st_mode)) { 2597 BDirectory subdir(&entry); 2598 (*_dirCount)++; 2599 status = FSRecursiveCalcSize(window, loopControl, &subdir, 2600 _runningSize, _fileCount, _dirCount); 2601 if (status != B_OK) 2602 return status; 2603 } else 2604 (*_fileCount)++; 2605 } 2606 return B_OK; 2607 } 2608 2609 2610 status_t 2611 CalcItemsAndSize(CopyLoopControl* loopControl, 2612 BObjectList<entry_ref>* refList, ssize_t blockSize, int32* totalCount, 2613 off_t* totalSize) 2614 { 2615 int32 fileCount = 0; 2616 int32 dirCount = 0; 2617 2618 // check block size for sanity 2619 if (blockSize < 0) { 2620 // This would point at an error to retrieve the block size from 2621 // the target volume. The code below cannot be used, it is only 2622 // meant to get the block size when item operations happen on 2623 // the source volume. 2624 blockSize = 2048; 2625 } else if (blockSize < 1024) { 2626 blockSize = 1024; 2627 if (entry_ref* ref = refList->ItemAt(0)) { 2628 // TODO: This assumes all entries in the list share the same 2629 // volume... 2630 BVolume volume(ref->device); 2631 if (volume.InitCheck() == B_OK) 2632 blockSize = volume.BlockSize(); 2633 } 2634 } 2635 // File systems like ReiserFS may advertize a large block size, but 2636 // stuff is still packed into blocks, so clamp maximum block size. 2637 if (blockSize > 8192) 2638 blockSize = 8192; 2639 2640 int32 num_items = refList->CountItems(); 2641 for (int32 i = 0; i < num_items; i++) { 2642 entry_ref* ref = refList->ItemAt(i); 2643 BEntry entry(ref); 2644 StatStruct statbuf; 2645 entry.GetStat(&statbuf); 2646 2647 if (loopControl->CheckUserCanceled()) 2648 return kUserCanceled; 2649 2650 if (S_ISDIR(statbuf.st_mode)) { 2651 BDirectory dir(&entry); 2652 dirCount++; 2653 (*totalSize) += blockSize; 2654 status_t result = FSRecursiveCalcSize(NULL, loopControl, &dir, 2655 totalSize, &fileCount, &dirCount); 2656 if (result != B_OK) 2657 return result; 2658 } else { 2659 fileCount++; 2660 (*totalSize) += statbuf.st_size + blockSize; 2661 } 2662 } 2663 2664 *totalCount += (fileCount + dirCount); 2665 return B_OK; 2666 } 2667 2668 2669 status_t 2670 FSGetTrashDir(BDirectory* trashDir, dev_t dev) 2671 { 2672 if (trashDir == NULL) 2673 return B_BAD_VALUE; 2674 2675 BVolume volume(dev); 2676 status_t result = volume.InitCheck(); 2677 if (result != B_OK) 2678 return result; 2679 2680 BPath path; 2681 result = find_directory(B_TRASH_DIRECTORY, &path, false, &volume); 2682 if (result != B_OK) 2683 return result; 2684 2685 result = trashDir->SetTo(path.Path()); 2686 if (result != B_OK) { 2687 // Trash directory does not exist yet, create it. 2688 result = create_directory(path.Path(), 0755); 2689 if (result != B_OK) 2690 return result; 2691 2692 result = trashDir->SetTo(path.Path()); 2693 if (result != B_OK) 2694 return result; 2695 2696 // make Trash directory invisible 2697 StatStruct sbuf; 2698 trashDir->GetStat(&sbuf); 2699 2700 PoseInfo poseInfo; 2701 poseInfo.fInvisible = true; 2702 poseInfo.fInitedDirectory = sbuf.st_ino; 2703 trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, 2704 sizeof(PoseInfo)); 2705 } 2706 2707 // set Trash icons (if they haven't already been set) 2708 attr_info attrInfo; 2709 size_t size; 2710 const void* data; 2711 if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2712 data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size); 2713 if (data != NULL) 2714 trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2715 } 2716 2717 if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2718 data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size); 2719 if (data != NULL) 2720 trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2721 } 2722 2723 if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2724 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, 2725 R_TrashIcon, &size); 2726 if (data != NULL) 2727 trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2728 } 2729 2730 return B_OK; 2731 } 2732 2733 2734 status_t 2735 FSGetDeskDir(BDirectory* deskDir) 2736 { 2737 if (deskDir == NULL) 2738 return B_BAD_VALUE; 2739 2740 BPath path; 2741 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true); 2742 if (result != B_OK) 2743 return result; 2744 2745 result = deskDir->SetTo(path.Path()); 2746 if (result != B_OK) 2747 return result; 2748 2749 // set Desktop icons (if they haven't already been set) 2750 attr_info attrInfo; 2751 size_t size; 2752 const void* data; 2753 if (deskDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2754 data = GetTrackerResources()->LoadResource('ICON', R_DeskIcon, &size); 2755 if (data != NULL) 2756 deskDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size); 2757 } 2758 2759 if (deskDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2760 data = GetTrackerResources()->LoadResource('MICN', R_DeskIcon, &size); 2761 if (data != NULL) 2762 deskDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size); 2763 } 2764 2765 if (deskDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) { 2766 data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, 2767 R_DeskIcon, &size); 2768 if (data != NULL) 2769 deskDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size); 2770 } 2771 2772 return B_OK; 2773 } 2774 2775 2776 status_t 2777 FSGetBootDeskDir(BDirectory* deskDir) 2778 { 2779 BVolume bootVolume; 2780 BVolumeRoster().GetBootVolume(&bootVolume); 2781 BPath path; 2782 2783 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, 2784 &bootVolume); 2785 if (result != B_OK) 2786 return result; 2787 2788 return deskDir->SetTo(path.Path()); 2789 } 2790 2791 2792 static bool 2793 FSIsDirFlavor(const BEntry* entry, directory_which directoryType) 2794 { 2795 StatStruct dir_stat; 2796 StatStruct entry_stat; 2797 BVolume volume; 2798 BPath path; 2799 2800 if (entry->GetStat(&entry_stat) != B_OK) 2801 return false; 2802 2803 if (volume.SetTo(entry_stat.st_dev) != B_OK) 2804 return false; 2805 2806 if (find_directory(directoryType, &path, false, &volume) != B_OK) 2807 return false; 2808 2809 stat(path.Path(), &dir_stat); 2810 2811 return dir_stat.st_ino == entry_stat.st_ino 2812 && dir_stat.st_dev == entry_stat.st_dev; 2813 } 2814 2815 2816 bool 2817 FSIsPrintersDir(const BEntry* entry) 2818 { 2819 return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY); 2820 } 2821 2822 2823 bool 2824 FSIsTrashDir(const BEntry* entry) 2825 { 2826 return FSIsDirFlavor(entry, B_TRASH_DIRECTORY); 2827 } 2828 2829 2830 bool 2831 FSIsDeskDir(const BEntry* entry) 2832 { 2833 BPath path; 2834 status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true); 2835 if (result != B_OK) 2836 return false; 2837 2838 BEntry entryToCompare(path.Path()); 2839 return entryToCompare == *entry; 2840 } 2841 2842 2843 bool 2844 FSIsHomeDir(const BEntry* entry) 2845 { 2846 return FSIsDirFlavor(entry, B_USER_DIRECTORY); 2847 } 2848 2849 2850 bool 2851 FSIsRootDir(const BEntry* entry) 2852 { 2853 BPath path; 2854 if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK) 2855 return false; 2856 2857 return strcmp(path.Path(), "/") == 0; 2858 } 2859 2860 2861 bool 2862 DirectoryMatchesOrContains(const BEntry* entry, directory_which which) 2863 { 2864 BPath path; 2865 if (find_directory(which, &path, false, NULL) != B_OK) 2866 return false; 2867 2868 BEntry dirEntry(path.Path()); 2869 if (dirEntry.InitCheck() != B_OK) 2870 return false; 2871 2872 if (dirEntry == *entry) 2873 // root level match 2874 return true; 2875 2876 BDirectory dir(&dirEntry); 2877 return dir.Contains(entry); 2878 } 2879 2880 2881 bool 2882 DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath, 2883 directory_which which) 2884 { 2885 BPath path; 2886 if (find_directory(which, &path, false, NULL) != B_OK) 2887 return false; 2888 2889 path.Append(additionalPath); 2890 BEntry dirEntry(path.Path()); 2891 if (dirEntry.InitCheck() != B_OK) 2892 return false; 2893 2894 if (dirEntry == *entry) 2895 // root level match 2896 return true; 2897 2898 BDirectory dir(&dirEntry); 2899 return dir.Contains(entry); 2900 } 2901 2902 2903 bool 2904 DirectoryMatches(const BEntry* entry, directory_which which) 2905 { 2906 BPath path; 2907 if (find_directory(which, &path, false, NULL) != B_OK) 2908 return false; 2909 2910 BEntry dirEntry(path.Path()); 2911 if (dirEntry.InitCheck() != B_OK) 2912 return false; 2913 2914 return dirEntry == *entry; 2915 } 2916 2917 2918 bool 2919 DirectoryMatches(const BEntry* entry, const char* additionalPath, 2920 directory_which which) 2921 { 2922 BPath path; 2923 if (find_directory(which, &path, false, NULL) != B_OK) 2924 return false; 2925 2926 path.Append(additionalPath); 2927 BEntry dirEntry(path.Path()); 2928 if (dirEntry.InitCheck() != B_OK) 2929 return false; 2930 2931 return dirEntry == *entry; 2932 } 2933 2934 2935 extern status_t 2936 FSFindTrackerSettingsDir(BPath* path, bool autoCreate) 2937 { 2938 status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path, 2939 autoCreate); 2940 if (result != B_OK) 2941 return result; 2942 2943 path->Append("Tracker"); 2944 2945 return mkdir(path->Path(), 0777) ? B_OK : errno; 2946 } 2947 2948 2949 bool 2950 FSInTrashDir(const entry_ref* ref) 2951 { 2952 BEntry entry(ref); 2953 if (entry.InitCheck() != B_OK) 2954 return false; 2955 2956 BDirectory trashDir; 2957 if (FSGetTrashDir(&trashDir, ref->device) != B_OK) 2958 return false; 2959 2960 return trashDir.Contains(&entry); 2961 } 2962 2963 2964 void 2965 FSEmptyTrash() 2966 { 2967 if (find_thread("_tracker_empty_trash_") != B_OK) { 2968 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", 2969 B_NORMAL_PRIORITY, NULL)); 2970 } 2971 } 2972 2973 2974 status_t 2975 empty_trash(void*) 2976 { 2977 // empty trash on all mounted volumes 2978 status_t status = B_OK; 2979 2980 TrackerCopyLoopControl loopControl(kTrashState); 2981 2982 // calculate the sum total of all items on all volumes in trash 2983 BObjectList<entry_ref> srcList; 2984 int32 totalCount = 0; 2985 off_t totalSize = 0; 2986 2987 BVolumeRoster volumeRoster; 2988 BVolume volume; 2989 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2990 if (volume.IsReadOnly() || !volume.IsPersistent()) 2991 continue; 2992 2993 BDirectory trashDirectory; 2994 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 2995 continue; 2996 2997 BEntry entry; 2998 trashDirectory.GetEntry(&entry); 2999 3000 entry_ref ref; 3001 entry.GetRef(&ref); 3002 srcList.AddItem(&ref); 3003 status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(), 3004 &totalCount, &totalSize); 3005 if (status != B_OK) 3006 break; 3007 3008 srcList.MakeEmpty(); 3009 3010 // don't count trash directory itself 3011 totalCount--; 3012 } 3013 3014 if (status == B_OK) { 3015 loopControl.Init(totalCount, totalCount); 3016 3017 volumeRoster.Rewind(); 3018 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 3019 if (volume.IsReadOnly() || !volume.IsPersistent()) 3020 continue; 3021 3022 BDirectory trashDirectory; 3023 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 3024 continue; 3025 3026 BEntry entry; 3027 trashDirectory.GetEntry(&entry); 3028 status = FSDeleteFolder(&entry, &loopControl, true, false); 3029 } 3030 } 3031 3032 if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) { 3033 BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"), 3034 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3035 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3036 alert->Go(); 3037 } 3038 3039 return B_OK; 3040 } 3041 3042 3043 status_t 3044 _DeleteTask(BObjectList<entry_ref>* list, bool confirm) 3045 { 3046 if (confirm) { 3047 BAlert* alert = new BAlert("", 3048 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr), 3049 B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"), 3050 B_TRANSLATE("Delete"), 3051 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 3052 3053 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3054 alert->SetShortcut(0, B_ESCAPE); 3055 alert->SetShortcut(1, 'm'); 3056 alert->SetShortcut(2, 'd'); 3057 3058 switch (alert->Go()) { 3059 case 0: 3060 delete list; 3061 return B_CANCELED; 3062 3063 case 1: 3064 default: 3065 FSMoveToTrash(list, NULL, false); 3066 return B_OK; 3067 3068 case 2: 3069 break; 3070 } 3071 } 3072 3073 TrackerCopyLoopControl loopControl(kDeleteState); 3074 3075 // calculate the sum total of all items on all volumes in trash 3076 int32 totalItems = 0; 3077 int64 totalSize = 0; 3078 3079 status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3080 &totalSize); 3081 if (status == B_OK) { 3082 loopControl.Init(totalItems, totalItems); 3083 3084 int32 count = list->CountItems(); 3085 for (int32 index = 0; index < count; index++) { 3086 entry_ref ref(*list->ItemAt(index)); 3087 BEntry entry(&ref); 3088 loopControl.UpdateStatus(ref.name, ref, 1, true); 3089 if (entry.IsDirectory()) 3090 status = FSDeleteFolder(&entry, &loopControl, true, true, true); 3091 else 3092 status = entry.Remove(); 3093 } 3094 3095 if (status != kTrashCanceled && status != kUserCanceled 3096 && status != B_OK) { 3097 BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"), 3098 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 3099 B_WARNING_ALERT); 3100 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3101 alert->Go(); 3102 } 3103 } 3104 3105 delete list; 3106 3107 return B_OK; 3108 } 3109 3110 status_t 3111 FSRecursiveCreateFolder(BPath path) 3112 { 3113 BEntry entry(path.Path()); 3114 if (entry.InitCheck() != B_OK) { 3115 BPath parentPath; 3116 status_t err = path.GetParent(&parentPath); 3117 if (err != B_OK) 3118 return err; 3119 3120 err = FSRecursiveCreateFolder(parentPath); 3121 if (err != B_OK) 3122 return err; 3123 } 3124 3125 entry.SetTo(path.Path()); 3126 if (entry.Exists()) 3127 return B_FILE_EXISTS; 3128 3129 BDirectory parent; 3130 entry.GetParent(&parent); 3131 parent.CreateDirectory(entry.Name(), NULL); 3132 3133 return B_OK; 3134 } 3135 3136 status_t 3137 _RestoreTask(BObjectList<entry_ref>* list) 3138 { 3139 TrackerCopyLoopControl loopControl(kRestoreFromTrashState); 3140 3141 // calculate the sum total of all items that will be restored 3142 int32 totalItems = 0; 3143 int64 totalSize = 0; 3144 3145 status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3146 &totalSize); 3147 if (err == B_OK) { 3148 loopControl.Init(totalItems, totalItems); 3149 3150 int32 count = list->CountItems(); 3151 for (int32 index = 0; index < count; index++) { 3152 entry_ref ref(*list->ItemAt(index)); 3153 BEntry entry(&ref); 3154 BPath originalPath; 3155 3156 loopControl.UpdateStatus(ref.name, ref, 1, true); 3157 3158 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 3159 continue; 3160 3161 BEntry originalEntry(originalPath.Path()); 3162 BPath parentPath; 3163 err = originalPath.GetParent(&parentPath); 3164 if (err != B_OK) 3165 continue; 3166 BEntry parentEntry(parentPath.Path()); 3167 3168 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 3169 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 3170 originalEntry.SetTo(originalPath.Path()); 3171 if (entry.InitCheck() != B_OK) 3172 continue; 3173 } 3174 } 3175 3176 if (!originalEntry.Exists()) { 3177 BDirectory dir(parentPath.Path()); 3178 if (dir.InitCheck() == B_OK) { 3179 const char* leafName = originalEntry.Name(); 3180 if (entry.MoveTo(&dir, leafName) == B_OK) { 3181 BNode node(&entry); 3182 if (node.InitCheck() == B_OK) 3183 node.RemoveAttr(kAttrOriginalPath); 3184 } 3185 } 3186 } 3187 3188 err = loopControl.CheckUserCanceled(); 3189 if (err != B_OK) 3190 break; 3191 } 3192 } 3193 3194 delete list; 3195 3196 return err; 3197 } 3198 3199 void 3200 FSCreateTrashDirs() 3201 { 3202 BVolume volume; 3203 BVolumeRoster roster; 3204 3205 roster.Rewind(); 3206 while (roster.GetNextVolume(&volume) == B_OK) { 3207 if (volume.IsReadOnly() || !volume.IsPersistent()) 3208 continue; 3209 3210 BDirectory trashDir; 3211 FSGetTrashDir(&trashDir, volume.Device()); 3212 } 3213 } 3214 3215 3216 status_t 3217 FSCreateNewFolder(entry_ref* ref) 3218 { 3219 node_ref node; 3220 node.device = ref->device; 3221 node.node = ref->directory; 3222 3223 BDirectory dir(&node); 3224 status_t result = dir.InitCheck(); 3225 if (result != B_OK) 3226 return result; 3227 3228 // ToDo: is that really necessary here? 3229 BString name(ref->name); 3230 FSMakeOriginalName(name, &dir, " -"); 3231 ref->set_name(name.String()); // update ref in case the folder got renamed 3232 3233 BDirectory newDir; 3234 result = dir.CreateDirectory(name.String(), &newDir); 3235 if (result != B_OK) 3236 return result; 3237 3238 BNodeInfo nodeInfo(&newDir); 3239 nodeInfo.SetType(B_DIR_MIMETYPE); 3240 3241 return result; 3242 } 3243 3244 3245 status_t 3246 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef, 3247 node_ref* newNode) 3248 { 3249 BDirectory dir(dirNode); 3250 status_t result = dir.InitCheck(); 3251 if (result == B_OK) { 3252 char name[B_FILE_NAME_LENGTH]; 3253 strlcpy(name, B_TRANSLATE("New folder"), sizeof(name)); 3254 3255 int fnum = 1; 3256 while (dir.Contains(name)) { 3257 // if base name already exists then add a number 3258 // TODO: move this logic to FSMakeOriginalName 3259 if (++fnum > 9) 3260 snprintf(name, sizeof(name), B_TRANSLATE("New folder%d"), fnum); 3261 else 3262 snprintf(name, sizeof(name), B_TRANSLATE("New folder %d"), fnum); 3263 } 3264 3265 BDirectory newDir; 3266 result = dir.CreateDirectory(name, &newDir); 3267 if (result == B_OK) { 3268 BEntry entry; 3269 newDir.GetEntry(&entry); 3270 entry.GetRef(newRef); 3271 entry.GetNodeRef(newNode); 3272 3273 BNodeInfo nodeInfo(&newDir); 3274 nodeInfo.SetType(B_DIR_MIMETYPE); 3275 3276 // add undo item 3277 NewFolderUndo undo(*newRef); 3278 return B_OK; 3279 } 3280 } 3281 3282 BAlert* alert = new BAlert("", 3283 B_TRANSLATE("Sorry, could not create a new folder."), 3284 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3285 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3286 alert->Go(); 3287 3288 return result; 3289 } 3290 3291 3292 ReadAttrResult 3293 ReadAttr(const BNode* node, const char* hostAttrName, 3294 const char* foreignAttrName, type_code type, off_t offset, void* buffer, 3295 size_t length, void (*swapFunc)(void*), bool isForeign) 3296 { 3297 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, 3298 length) == (ssize_t)length) { 3299 return kReadAttrNativeOK; 3300 } 3301 3302 // PRINT(("trying %s\n", foreignAttrName)); 3303 // try the other endianness 3304 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) 3305 != (ssize_t)length) { 3306 return kReadAttrFailed; 3307 } 3308 3309 // PRINT(("got %s\n", foreignAttrName)); 3310 if (!swapFunc) 3311 return kReadAttrForeignOK; 3312 3313 (swapFunc)(buffer); 3314 // run the endian swapper 3315 3316 return kReadAttrForeignOK; 3317 } 3318 3319 3320 ReadAttrResult 3321 GetAttrInfo(const BNode* node, const char* hostAttrName, 3322 const char* foreignAttrName, type_code* type, size_t* size) 3323 { 3324 attr_info info; 3325 3326 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { 3327 if (type) 3328 *type = info.type; 3329 if (size) 3330 *size = (size_t)info.size; 3331 3332 return kReadAttrNativeOK; 3333 } 3334 3335 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { 3336 if (type) 3337 *type = info.type; 3338 if (size) 3339 *size = (size_t)info.size; 3340 3341 return kReadAttrForeignOK; 3342 } 3343 return kReadAttrFailed; 3344 } 3345 3346 3347 status_t 3348 FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref) 3349 { 3350 node_ref nodeRef; 3351 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3352 if (VirtualDirectoryManager* manager 3353 = VirtualDirectoryManager::Instance()) { 3354 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3355 if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref, 3356 nodeRef)) { 3357 return B_OK; 3358 } 3359 } 3360 } 3361 3362 status_t error; 3363 BDirectory parent; 3364 BEntry parentEntry; 3365 if ((error = entry.GetParent(&parent)) != B_OK 3366 || (error = parent.GetEntry(&parentEntry)) != B_OK 3367 || (error = parentEntry.GetRef(&_ref)) != B_OK) { 3368 return error; 3369 } 3370 3371 return B_OK; 3372 } 3373 3374 3375 status_t 3376 FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry) 3377 { 3378 node_ref nodeRef; 3379 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3380 if (VirtualDirectoryManager* manager 3381 = VirtualDirectoryManager::Instance()) { 3382 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3383 entry_ref parentRef; 3384 if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef, 3385 nodeRef)) { 3386 return _entry.SetTo(&parentRef); 3387 } 3388 } 3389 } 3390 3391 return entry.GetParent(&_entry); 3392 } 3393 3394 3395 status_t 3396 FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node) 3397 { 3398 entry_ref ref; 3399 status_t error = FSGetParentVirtualDirectoryAware(entry, ref); 3400 if (error == B_OK) 3401 error = _node.SetTo(&ref); 3402 3403 return error; 3404 } 3405 3406 3407 // launching code 3408 3409 static status_t 3410 TrackerOpenWith(const BMessage* refs) 3411 { 3412 BMessage clone(*refs); 3413 3414 ASSERT(dynamic_cast<TTracker*>(be_app) != NULL); 3415 ASSERT(clone.what != 0); 3416 3417 clone.AddInt32("launchUsingSelector", 0); 3418 // runs the Open With window 3419 be_app->PostMessage(&clone); 3420 3421 return B_OK; 3422 } 3423 3424 3425 static void 3426 AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on), 3427 const entry_ref* appRef, const BMessage* refs, bool openWithOK) 3428 { 3429 BMessage* task = new BMessage; 3430 task->AddPointer("function", (void*)func); 3431 task->AddMessage("refs", refs); 3432 task->AddBool("openWithOK", openWithOK); 3433 if (appRef != NULL) 3434 task->AddRef("appRef", appRef); 3435 3436 extern BLooper* gLaunchLooper; 3437 gLaunchLooper->PostMessage(task); 3438 } 3439 3440 3441 static bool 3442 SniffIfGeneric(const entry_ref* ref) 3443 { 3444 BNode node(ref); 3445 char type[B_MIME_TYPE_LENGTH]; 3446 BNodeInfo info(&node); 3447 if (info.GetType(type) == B_OK 3448 && strcasecmp(type, B_FILE_MIME_TYPE) != 0) { 3449 // already has a type and it's not octet stream 3450 return false; 3451 } 3452 3453 BPath path(ref); 3454 if (path.Path()) { 3455 // force a mimeset 3456 node.RemoveAttr(kAttrMIMEType); 3457 update_mime_info(path.Path(), 0, 1, 1); 3458 } 3459 3460 return true; 3461 } 3462 3463 3464 static void 3465 SniffIfGeneric(const BMessage* refs) 3466 { 3467 entry_ref ref; 3468 for (int32 index = 0; ; index++) { 3469 if (refs->FindRef("refs", index, &ref) != B_OK) 3470 break; 3471 SniffIfGeneric(&ref); 3472 } 3473 } 3474 3475 3476 static void 3477 _TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs, 3478 bool openWithOK) 3479 { 3480 team_id team; 3481 3482 status_t error = B_ERROR; 3483 BString alertString; 3484 3485 for (int32 mimesetIt = 0; ; mimesetIt++) { 3486 error = be_roster->Launch(appRef, refs, &team); 3487 if (error == B_ALREADY_RUNNING) 3488 // app already running, not really an error 3489 error = B_OK; 3490 3491 if (error == B_OK) 3492 break; 3493 3494 if (mimesetIt > 0) 3495 break; 3496 3497 // failed to open, try mimesetting the refs and launching again 3498 SniffIfGeneric(refs); 3499 } 3500 3501 if (error == B_OK) { 3502 // close possible parent window, if specified 3503 const node_ref* nodeToClose = 0; 3504 ssize_t numBytes; 3505 if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE, 3506 (const void**)&nodeToClose, &numBytes) == B_OK 3507 && nodeToClose != NULL) { 3508 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 3509 if (tracker != NULL) 3510 tracker->CloseParent(*nodeToClose); 3511 } 3512 } else { 3513 alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). ")); 3514 alertString.ReplaceFirst("%name", appRef->name); 3515 alertString.ReplaceFirst("%error", strerror(error)); 3516 if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) { 3517 alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3518 BAlert* alert = new BAlert("", alertString.String(), 3519 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3520 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3521 alert->SetShortcut(0, B_ESCAPE); 3522 if (alert->Go() == 1) 3523 error = TrackerOpenWith(refs); 3524 } else { 3525 BAlert* alert = new BAlert("", alertString.String(), 3526 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3527 B_WARNING_ALERT); 3528 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3529 alert->Go(); 3530 } 3531 } 3532 } 3533 3534 3535 extern "C" char** environ; 3536 3537 3538 static status_t 3539 LoaderErrorDetails(const entry_ref* app, BString &details) 3540 { 3541 BPath path; 3542 BEntry appEntry(app, true); 3543 3544 status_t result = appEntry.GetPath(&path); 3545 if (result != B_OK) 3546 return result; 3547 3548 char* argv[2] = { const_cast<char*>(path.Path()), 0}; 3549 3550 port_id errorPort = create_port(1, "Tracker loader error"); 3551 3552 // count environment variables 3553 int32 envCount = 0; 3554 while (environ[envCount] != NULL) 3555 envCount++; 3556 3557 char** flatArgs = NULL; 3558 size_t flatArgsSize; 3559 result = __flatten_process_args((const char**)argv, 1, 3560 environ, &envCount, argv[0], &flatArgs, &flatArgsSize); 3561 if (result != B_OK) 3562 return result; 3563 3564 result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount, 3565 B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0); 3566 if (result == B_OK) { 3567 // we weren't supposed to be able to start the application... 3568 return B_ERROR; 3569 } 3570 3571 // read error message from port and construct details string 3572 3573 ssize_t bufferSize; 3574 3575 do { 3576 bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0); 3577 } while (bufferSize == B_INTERRUPTED); 3578 3579 if (bufferSize <= B_OK) { 3580 delete_port(errorPort); 3581 return bufferSize; 3582 } 3583 3584 uint8* buffer = (uint8*)malloc(bufferSize); 3585 if (buffer == NULL) { 3586 delete_port(errorPort); 3587 return B_NO_MEMORY; 3588 } 3589 3590 bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize, 3591 B_RELATIVE_TIMEOUT, 0); 3592 delete_port(errorPort); 3593 3594 if (bufferSize < B_OK) { 3595 free(buffer); 3596 return bufferSize; 3597 } 3598 3599 BMessage message; 3600 result = message.Unflatten((const char*)buffer); 3601 free(buffer); 3602 3603 if (result != B_OK) 3604 return result; 3605 3606 int32 errorCode = B_ERROR; 3607 result = message.FindInt32("error", &errorCode); 3608 if (result != B_OK) 3609 return result; 3610 3611 const char* detailName = NULL; 3612 switch (errorCode) { 3613 case B_MISSING_LIBRARY: 3614 detailName = "missing library"; 3615 break; 3616 3617 case B_MISSING_SYMBOL: 3618 detailName = "missing symbol"; 3619 break; 3620 } 3621 3622 if (detailName == NULL) 3623 return B_ERROR; 3624 3625 const char* detail; 3626 for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK; 3627 i++) { 3628 if (i > 0) 3629 details += ", "; 3630 details += detail; 3631 } 3632 3633 return B_OK; 3634 } 3635 3636 3637 static void 3638 _TrackerLaunchDocuments(const entry_ref*, const BMessage* refs, 3639 bool openWithOK) 3640 { 3641 if (refs == NULL) 3642 return; 3643 3644 BMessage copyOfRefs(*refs); 3645 3646 entry_ref documentRef; 3647 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) { 3648 // nothing to launch, we are done 3649 return; 3650 } 3651 3652 status_t error = B_ERROR; 3653 entry_ref app; 3654 BMessage* refsToPass = NULL; 3655 BString alertString; 3656 const char* alternative = 0; 3657 3658 for (int32 mimesetIt = 0; ; mimesetIt++) { 3659 alertString = ""; 3660 error = be_roster->FindApp(&documentRef, &app); 3661 3662 if (error != B_OK && mimesetIt == 0) { 3663 SniffIfGeneric(©OfRefs); 3664 continue; 3665 } 3666 3667 if (error != B_OK) { 3668 alertString.SetTo(B_TRANSLATE("Could not find an application to " 3669 "open \"%name\" (%error). ")); 3670 alertString.ReplaceFirst("%name", documentRef.name); 3671 alertString.ReplaceFirst("%error", strerror(error)); 3672 if (openWithOK) 3673 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3674 3675 break; 3676 } else { 3677 BEntry appEntry(&app, true); 3678 for (int32 index = 0;;) { 3679 // remove the app itself from the refs received so we don't 3680 // try to open ourselves 3681 entry_ref ref; 3682 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) 3683 break; 3684 3685 // deal with symlinks properly 3686 BEntry documentEntry(&ref, true); 3687 if (appEntry == documentEntry) { 3688 PRINT(("stripping %s, app %s \n", ref.name, app.name)); 3689 copyOfRefs.RemoveData("refs", index); 3690 } else { 3691 PRINT(("leaving %s, app %s \n", ref.name, app.name)); 3692 index++; 3693 } 3694 } 3695 3696 refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; 3697 team_id team; 3698 error = be_roster->Launch(&app, refsToPass, &team); 3699 if (error == B_ALREADY_RUNNING) 3700 // app already running, not really an error 3701 error = B_OK; 3702 if (error == B_OK || mimesetIt != 0) 3703 break; 3704 if (error == B_LAUNCH_FAILED_EXECUTABLE) { 3705 BVolume volume(documentRef.device); 3706 if (volume.IsReadOnly()) { 3707 BMimeType type; 3708 error = BMimeType::GuessMimeType(&documentRef, &type); 3709 if (error != B_OK) 3710 break; 3711 error = be_roster->FindApp(type.Type(), &app); 3712 if (error != B_OK) 3713 break; 3714 error = be_roster->Launch(&app, refs, &team); 3715 if (error == B_ALREADY_RUNNING) 3716 // app already running, not really an error 3717 error = B_OK; 3718 if (error == B_OK || mimesetIt != 0) 3719 break; 3720 } 3721 } 3722 3723 SniffIfGeneric(©OfRefs); 3724 } 3725 } 3726 3727 if (error != B_OK && alertString.Length() == 0) { 3728 BString loaderErrorString; 3729 bool openedDocuments = true; 3730 3731 if (!refsToPass) { 3732 // we just double clicked the app itself, do not offer to 3733 // find a handling app 3734 openWithOK = false; 3735 openedDocuments = false; 3736 } 3737 if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) { 3738 // We know it's an executable, but something unsupported 3739 alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported " 3740 "executable.")); 3741 alertString.ReplaceFirst("%name", app.name); 3742 } else if (error == B_LEGACY_EXECUTABLE && !refsToPass) { 3743 // For the moment, this marks an old R3 binary, we may want to 3744 // extend it to gcc2 binaries someday post R1 3745 alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. " 3746 "Please obtain an updated version or recompile " 3747 "the application.")); 3748 alertString.ReplaceFirst("%name", app.name); 3749 } else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { 3750 alertString.SetTo(B_TRANSLATE("Could not open \"%name\". " 3751 "The file is mistakenly marked as executable. ")); 3752 alertString.ReplaceFirst("%name", app.name); 3753 3754 if (!openWithOK) { 3755 // offer the possibility to change the permissions 3756 3757 alertString << B_TRANSLATE("\nShould this be fixed?"); 3758 BAlert* alert = new BAlert("", alertString.String(), 3759 B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0, 3760 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3761 alert->SetShortcut(0, B_ESCAPE); 3762 if (alert->Go() == 1) { 3763 BEntry entry(&documentRef); 3764 mode_t permissions; 3765 3766 error = entry.GetPermissions(&permissions); 3767 if (error == B_OK) { 3768 error = entry.SetPermissions(permissions 3769 & ~(S_IXUSR | S_IXGRP | S_IXOTH)); 3770 } 3771 if (error == B_OK) { 3772 // we updated the permissions, so let's try again 3773 _TrackerLaunchDocuments(NULL, refs, false); 3774 return; 3775 } else { 3776 alertString.SetTo(B_TRANSLATE("Could not update " 3777 "permissions of file \"%name\". %error")); 3778 alertString.ReplaceFirst("%name", app.name); 3779 alertString.ReplaceFirst("%error", strerror(error)); 3780 } 3781 } else 3782 return; 3783 } 3784 3785 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3786 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { 3787 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3788 "because application \"%app\" is in the Trash. ")); 3789 alertString.ReplaceFirst("%document", documentRef.name); 3790 alertString.ReplaceFirst("%app", app.name); 3791 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3792 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { 3793 alertString.SetTo( 3794 B_TRANSLATE("Could not open \"%name\" (%error). ")); 3795 alertString.ReplaceFirst("%name", documentRef.name); 3796 alertString.ReplaceFirst("%error", strerror(error)); 3797 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3798 } else if (error == B_MISSING_SYMBOL 3799 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3800 if (openedDocuments) { 3801 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3802 "with application \"%app\" (Missing symbol: %symbol). " 3803 "\n")); 3804 alertString.ReplaceFirst("%document", documentRef.name); 3805 alertString.ReplaceFirst("%app", app.name); 3806 alertString.ReplaceFirst("%symbol", 3807 loaderErrorString.String()); 3808 } else { 3809 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3810 "(Missing symbol: %symbol). \n")); 3811 alertString.ReplaceFirst("%document", documentRef.name); 3812 alertString.ReplaceFirst("%symbol", 3813 loaderErrorString.String()); 3814 } 3815 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3816 } else if (error == B_MISSING_LIBRARY 3817 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3818 if (openedDocuments) { 3819 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3820 "with application \"%app\" (Missing libraries: %library). " 3821 "\n")); 3822 alertString.ReplaceFirst("%document", documentRef.name); 3823 alertString.ReplaceFirst("%app", app.name); 3824 alertString.ReplaceFirst("%library", 3825 loaderErrorString.String()); 3826 } else { 3827 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3828 "(Missing libraries: %library). \n")); 3829 alertString.ReplaceFirst("%document", documentRef.name); 3830 alertString.ReplaceFirst("%library", 3831 loaderErrorString.String()); 3832 } 3833 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3834 } else { 3835 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with " 3836 "application \"%app\" (%error). ")); 3837 alertString.ReplaceFirst("%document", documentRef.name); 3838 alertString.ReplaceFirst("%app", app.name); 3839 alertString.ReplaceFirst("%error", strerror(error)); 3840 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3841 } 3842 } 3843 3844 if (error != B_OK) { 3845 if (openWithOK) { 3846 ASSERT(alternative); 3847 alertString << alternative; 3848 BAlert* alert = new BAlert("", alertString.String(), 3849 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3850 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3851 alert->SetShortcut(0, B_ESCAPE); 3852 if (alert->Go() == 1) 3853 error = TrackerOpenWith(refs); 3854 } else { 3855 BAlert* alert = new BAlert("", alertString.String(), 3856 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3857 B_WARNING_ALERT); 3858 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3859 alert->Go(); 3860 } 3861 } 3862 } 3863 3864 // the following three calls don't return any reasonable error codes, 3865 // should fix that, making them void 3866 3867 status_t 3868 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async, 3869 bool openWithOK) 3870 { 3871 if (!async) 3872 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); 3873 else { 3874 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, 3875 openWithOK); 3876 } 3877 3878 return B_OK; 3879 } 3880 3881 status_t 3882 TrackerLaunch(const entry_ref* appRef, bool async) 3883 { 3884 if (!async) 3885 _TrackerLaunchAppWithDocuments(appRef, NULL, false); 3886 else 3887 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); 3888 3889 return B_OK; 3890 } 3891 3892 status_t 3893 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK) 3894 { 3895 if (!async) 3896 _TrackerLaunchDocuments(NULL, refs, openWithOK); 3897 else 3898 AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK); 3899 3900 return B_OK; 3901 } 3902 3903 3904 // external launch calls; need to be robust, work if Tracker is not running 3905 3906 3907 #if !B_BEOS_VERSION_DANO 3908 _IMPEXP_TRACKER 3909 #endif 3910 status_t 3911 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived, 3912 bool async, bool openWithOK) 3913 { 3914 return TrackerLaunch(application, refsReceived, async, openWithOK); 3915 } 3916 3917 3918 #if !B_BEOS_VERSION_DANO 3919 _IMPEXP_TRACKER 3920 #endif 3921 status_t 3922 FSOpenWith(BMessage* listOfRefs) 3923 { 3924 status_t result = B_ERROR; 3925 listOfRefs->what = B_REFS_RECEIVED; 3926 3927 if (dynamic_cast<TTracker*>(be_app) != NULL) 3928 result = TrackerOpenWith(listOfRefs); 3929 else 3930 ASSERT(!"not yet implemented"); 3931 3932 return result; 3933 } 3934 3935 3936 // legacy calls, need for compatibility 3937 3938 3939 void 3940 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents) 3941 { 3942 TrackerLaunch(executable, documents, true); 3943 delete documents; 3944 } 3945 3946 3947 status_t 3948 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs) 3949 { 3950 BMessage temp(B_REFS_RECEIVED); 3951 if (listOfRefs == NULL) { 3952 ASSERT(ref != NULL); 3953 temp.AddRef("refs", ref); 3954 listOfRefs = &temp; 3955 } 3956 FSOpenWith(listOfRefs); 3957 3958 return B_OK; 3959 } 3960 3961 3962 status_t 3963 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async) 3964 { 3965 if (refs != NULL) 3966 refs->what = B_REFS_RECEIVED; 3967 3968 status_t result = TrackerLaunch(appRef, refs, async, true); 3969 delete refs; 3970 3971 return result; 3972 } 3973 3974 3975 void 3976 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace) 3977 { 3978 FSLaunchItem(appRef, refs, workspace, true); 3979 } 3980 3981 3982 // Get the original path of an entry in the trash 3983 status_t 3984 FSGetOriginalPath(BEntry* entry, BPath* result) 3985 { 3986 status_t err; 3987 entry_ref ref; 3988 err = entry->GetRef(&ref); 3989 if (err != B_OK) 3990 return err; 3991 3992 // Only call the routine for entries in the trash 3993 if (!FSInTrashDir(&ref)) 3994 return B_ERROR; 3995 3996 BNode node(entry); 3997 BString originalPath; 3998 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { 3999 // We're in luck, the entry has the original path in an attribute 4000 err = result->SetTo(originalPath.String()); 4001 return err; 4002 } 4003 4004 // Iterate the parent directories to find one with 4005 // the original path attribute 4006 BEntry parent(*entry); 4007 err = parent.InitCheck(); 4008 if (err != B_OK) 4009 return err; 4010 4011 // walk up the directory structure until we find a node 4012 // with original path attribute 4013 do { 4014 // move to the parent of this node 4015 err = parent.GetParent(&parent); 4016 if (err != B_OK) 4017 return err; 4018 4019 // return if we are at the root of the trash 4020 if (FSIsTrashDir(&parent)) 4021 return B_ENTRY_NOT_FOUND; 4022 4023 // get the parent as a node 4024 err = node.SetTo(&parent); 4025 if (err != B_OK) 4026 return err; 4027 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); 4028 4029 // Found the attribute, figure out there this file 4030 // used to live, based on the successfully-read attribute 4031 err = result->SetTo(originalPath.String()); 4032 if (err != B_OK) 4033 return err; 4034 4035 BPath path, pathParent; 4036 err = parent.GetPath(&pathParent); 4037 if (err != B_OK) 4038 return err; 4039 4040 err = entry->GetPath(&path); 4041 if (err != B_OK) 4042 return err; 4043 4044 result->Append(path.Path() + strlen(pathParent.Path()) + 1); 4045 // compute the new path by appending the offset of 4046 // the item we are locating, to the original path 4047 // of the parent 4048 4049 return B_OK; 4050 } 4051 4052 4053 directory_which 4054 WellKnowEntryList::Match(const node_ref* node) 4055 { 4056 const WellKnownEntry* result = MatchEntry(node); 4057 if (result != NULL) 4058 return result->which; 4059 4060 return (directory_which)-1; 4061 } 4062 4063 4064 const WellKnowEntryList::WellKnownEntry* 4065 WellKnowEntryList::MatchEntry(const node_ref* node) 4066 { 4067 if (self == NULL) 4068 self = new WellKnowEntryList(); 4069 4070 return self->MatchEntryCommon(node); 4071 } 4072 4073 4074 const WellKnowEntryList::WellKnownEntry* 4075 WellKnowEntryList::MatchEntryCommon(const node_ref* node) 4076 { 4077 uint32 count = entries.size(); 4078 for (uint32 index = 0; index < count; index++) { 4079 if (*node == entries[index].node) 4080 return &entries[index]; 4081 } 4082 4083 return NULL; 4084 } 4085 4086 4087 void 4088 WellKnowEntryList::Quit() 4089 { 4090 delete self; 4091 self = NULL; 4092 } 4093 4094 4095 void 4096 WellKnowEntryList::AddOne(directory_which which, const char* name) 4097 { 4098 BPath path; 4099 if (find_directory(which, &path, true) != B_OK) 4100 return; 4101 4102 BEntry entry(path.Path(), true); 4103 node_ref node; 4104 if (entry.GetNodeRef(&node) != B_OK) 4105 return; 4106 4107 entries.push_back(WellKnownEntry(&node, which, name)); 4108 } 4109 4110 4111 void 4112 WellKnowEntryList::AddOne(directory_which which, directory_which base, 4113 const char* extra, const char* name) 4114 { 4115 BPath path; 4116 if (find_directory(base, &path, true) != B_OK) 4117 return; 4118 4119 path.Append(extra); 4120 BEntry entry(path.Path(), true); 4121 node_ref node; 4122 if (entry.GetNodeRef(&node) != B_OK) 4123 return; 4124 4125 entries.push_back(WellKnownEntry(&node, which, name)); 4126 } 4127 4128 4129 void 4130 WellKnowEntryList::AddOne(directory_which which, const char* path, 4131 const char* name) 4132 { 4133 BEntry entry(path, true); 4134 node_ref node; 4135 if (entry.GetNodeRef(&node) != B_OK) 4136 return; 4137 4138 entries.push_back(WellKnownEntry(&node, which, name)); 4139 } 4140 4141 4142 WellKnowEntryList::WellKnowEntryList() 4143 { 4144 AddOne(B_SYSTEM_DIRECTORY, "system"); 4145 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); 4146 AddOne(B_USER_DIRECTORY, "home"); 4147 4148 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); 4149 AddOne(B_USER_FONTS_DIRECTORY, "fonts"); 4150 4151 AddOne(B_BEOS_APPS_DIRECTORY, "apps"); 4152 AddOne(B_APPS_DIRECTORY, "apps"); 4153 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, 4154 B_USER_DESKBAR_DIRECTORY, "Applications", "apps"); 4155 4156 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); 4157 AddOne(B_PREFERENCES_DIRECTORY, "preferences"); 4158 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, 4159 B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences"); 4160 4161 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", 4162 "mail"); 4163 4164 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, 4165 "queries", "queries"); 4166 4167 AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop"); 4168 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, 4169 B_USER_DESKBAR_DIRECTORY, "Development", "develop"); 4170 4171 AddOne(B_USER_CONFIG_DIRECTORY, "config"); 4172 4173 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, 4174 "people", "people"); 4175 4176 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, 4177 "downloads", "downloads"); 4178 } 4179 4180 WellKnowEntryList* WellKnowEntryList::self = NULL; 4181 4182 } // namespace BPrivate 4183