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 FSInDeskDir(const entry_ref* ref) 2845 { 2846 BEntry entry(ref); 2847 if (entry.InitCheck() != B_OK) 2848 return false; 2849 2850 BPath path; 2851 if (find_directory(B_DESKTOP_DIRECTORY, &path, true) != B_OK) 2852 return false; 2853 2854 BDirectory desktop(path.Path()); 2855 return desktop.Contains(&entry); 2856 } 2857 2858 2859 bool 2860 FSIsHomeDir(const BEntry* entry) 2861 { 2862 return FSIsDirFlavor(entry, B_USER_DIRECTORY); 2863 } 2864 2865 2866 bool 2867 FSIsQueriesDir(const entry_ref* ref) 2868 { 2869 const BEntry entry(ref); 2870 return DirectoryMatches(&entry, "queries", B_USER_DIRECTORY); 2871 } 2872 2873 2874 bool 2875 FSIsRootDir(const BEntry* entry) 2876 { 2877 BPath path; 2878 if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK) 2879 return false; 2880 2881 return strcmp(path.Path(), "/") == 0; 2882 } 2883 2884 2885 bool 2886 FSInRootDir(const entry_ref* ref) 2887 { 2888 BEntry entry(ref); 2889 if (entry.InitCheck() != B_OK) 2890 return false; 2891 2892 BDirectory root("/"); 2893 return root.Contains(&entry); 2894 } 2895 2896 2897 bool 2898 DirectoryMatchesOrContains(const BEntry* entry, directory_which which) 2899 { 2900 BPath path; 2901 if (find_directory(which, &path, false, NULL) != B_OK) 2902 return false; 2903 2904 BEntry dirEntry(path.Path()); 2905 if (dirEntry.InitCheck() != B_OK) 2906 return false; 2907 2908 if (dirEntry == *entry) 2909 // root level match 2910 return true; 2911 2912 BDirectory dir(&dirEntry); 2913 return dir.Contains(entry); 2914 } 2915 2916 2917 bool 2918 DirectoryMatchesOrContains(const BEntry* entry, const char* additionalPath, 2919 directory_which which) 2920 { 2921 BPath path; 2922 if (find_directory(which, &path, false, NULL) != B_OK) 2923 return false; 2924 2925 path.Append(additionalPath); 2926 BEntry dirEntry(path.Path()); 2927 if (dirEntry.InitCheck() != B_OK) 2928 return false; 2929 2930 if (dirEntry == *entry) 2931 // root level match 2932 return true; 2933 2934 BDirectory dir(&dirEntry); 2935 return dir.Contains(entry); 2936 } 2937 2938 2939 bool 2940 DirectoryMatches(const BEntry* entry, directory_which which) 2941 { 2942 BPath path; 2943 if (find_directory(which, &path, false, NULL) != B_OK) 2944 return false; 2945 2946 BEntry dirEntry(path.Path()); 2947 if (dirEntry.InitCheck() != B_OK) 2948 return false; 2949 2950 return dirEntry == *entry; 2951 } 2952 2953 2954 bool 2955 DirectoryMatches(const BEntry* entry, const char* additionalPath, 2956 directory_which which) 2957 { 2958 BPath path; 2959 if (find_directory(which, &path, false, NULL) != B_OK) 2960 return false; 2961 2962 path.Append(additionalPath); 2963 BEntry dirEntry(path.Path()); 2964 if (dirEntry.InitCheck() != B_OK) 2965 return false; 2966 2967 return dirEntry == *entry; 2968 } 2969 2970 2971 extern status_t 2972 FSFindTrackerSettingsDir(BPath* path, bool autoCreate) 2973 { 2974 status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path, 2975 autoCreate); 2976 if (result != B_OK) 2977 return result; 2978 2979 path->Append("Tracker"); 2980 2981 return mkdir(path->Path(), 0777) ? B_OK : errno; 2982 } 2983 2984 2985 bool 2986 FSInTrashDir(const entry_ref* ref) 2987 { 2988 BEntry entry(ref); 2989 if (entry.InitCheck() != B_OK) 2990 return false; 2991 2992 BDirectory trashDir; 2993 if (FSGetTrashDir(&trashDir, ref->device) != B_OK) 2994 return false; 2995 2996 return trashDir.Contains(&entry); 2997 } 2998 2999 3000 void 3001 FSEmptyTrash() 3002 { 3003 if (find_thread("_tracker_empty_trash_") != B_OK) { 3004 resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", 3005 B_NORMAL_PRIORITY, NULL)); 3006 } 3007 } 3008 3009 3010 status_t 3011 empty_trash(void*) 3012 { 3013 // empty trash on all mounted volumes 3014 status_t status = B_OK; 3015 3016 TrackerCopyLoopControl loopControl(kTrashState); 3017 3018 // calculate the sum total of all items on all volumes in trash 3019 BObjectList<entry_ref> srcList; 3020 int32 totalCount = 0; 3021 off_t totalSize = 0; 3022 3023 BVolumeRoster volumeRoster; 3024 BVolume volume; 3025 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 3026 if (volume.IsReadOnly() || !volume.IsPersistent()) 3027 continue; 3028 3029 BDirectory trashDirectory; 3030 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 3031 continue; 3032 3033 BEntry entry; 3034 trashDirectory.GetEntry(&entry); 3035 3036 entry_ref ref; 3037 entry.GetRef(&ref); 3038 srcList.AddItem(&ref); 3039 status = CalcItemsAndSize(&loopControl, &srcList, volume.BlockSize(), 3040 &totalCount, &totalSize); 3041 if (status != B_OK) 3042 break; 3043 3044 srcList.MakeEmpty(); 3045 3046 // don't count trash directory itself 3047 totalCount--; 3048 } 3049 3050 if (status == B_OK) { 3051 loopControl.Init(totalCount, totalCount); 3052 3053 volumeRoster.Rewind(); 3054 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 3055 if (volume.IsReadOnly() || !volume.IsPersistent()) 3056 continue; 3057 3058 BDirectory trashDirectory; 3059 if (FSGetTrashDir(&trashDirectory, volume.Device()) != B_OK) 3060 continue; 3061 3062 BEntry entry; 3063 trashDirectory.GetEntry(&entry); 3064 status = FSDeleteFolder(&entry, &loopControl, true, false); 3065 } 3066 } 3067 3068 if (status != B_OK && status != kTrashCanceled && status != kUserCanceled) { 3069 BAlert* alert = new BAlert("", B_TRANSLATE("Error emptying Trash"), 3070 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3071 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3072 alert->Go(); 3073 } 3074 3075 return B_OK; 3076 } 3077 3078 3079 status_t 3080 _DeleteTask(BObjectList<entry_ref>* list, bool confirm) 3081 { 3082 if (confirm) { 3083 BAlert* alert = new BAlert("", 3084 B_TRANSLATE_NOCOLLECT(kDeleteConfirmationStr), 3085 B_TRANSLATE("Cancel"), B_TRANSLATE("Move to Trash"), 3086 B_TRANSLATE("Delete"), 3087 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 3088 3089 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3090 alert->SetShortcut(0, B_ESCAPE); 3091 alert->SetShortcut(1, 'm'); 3092 alert->SetShortcut(2, 'd'); 3093 3094 switch (alert->Go()) { 3095 case 0: 3096 delete list; 3097 return B_CANCELED; 3098 3099 case 1: 3100 default: 3101 FSMoveToTrash(list, NULL, false); 3102 return B_OK; 3103 3104 case 2: 3105 break; 3106 } 3107 } 3108 3109 TrackerCopyLoopControl loopControl(kDeleteState); 3110 3111 // calculate the sum total of all items on all volumes in trash 3112 int32 totalItems = 0; 3113 int64 totalSize = 0; 3114 3115 status_t status = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3116 &totalSize); 3117 if (status == B_OK) { 3118 loopControl.Init(totalItems, totalItems); 3119 3120 int32 count = list->CountItems(); 3121 for (int32 index = 0; index < count; index++) { 3122 entry_ref ref(*list->ItemAt(index)); 3123 BEntry entry(&ref); 3124 loopControl.UpdateStatus(ref.name, ref, 1, true); 3125 if (entry.IsDirectory()) 3126 status = FSDeleteFolder(&entry, &loopControl, true, true, true); 3127 else 3128 status = entry.Remove(); 3129 } 3130 3131 if (status != kTrashCanceled && status != kUserCanceled 3132 && status != B_OK) { 3133 BAlert* alert = new BAlert("", B_TRANSLATE("Error deleting items"), 3134 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 3135 B_WARNING_ALERT); 3136 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3137 alert->Go(); 3138 } 3139 } 3140 3141 delete list; 3142 3143 return B_OK; 3144 } 3145 3146 status_t 3147 FSRecursiveCreateFolder(BPath path) 3148 { 3149 BEntry entry(path.Path()); 3150 if (entry.InitCheck() != B_OK) { 3151 BPath parentPath; 3152 status_t err = path.GetParent(&parentPath); 3153 if (err != B_OK) 3154 return err; 3155 3156 err = FSRecursiveCreateFolder(parentPath); 3157 if (err != B_OK) 3158 return err; 3159 } 3160 3161 entry.SetTo(path.Path()); 3162 if (entry.Exists()) 3163 return B_FILE_EXISTS; 3164 3165 BDirectory parent; 3166 entry.GetParent(&parent); 3167 parent.CreateDirectory(entry.Name(), NULL); 3168 3169 return B_OK; 3170 } 3171 3172 status_t 3173 _RestoreTask(BObjectList<entry_ref>* list) 3174 { 3175 TrackerCopyLoopControl loopControl(kRestoreFromTrashState); 3176 3177 // calculate the sum total of all items that will be restored 3178 int32 totalItems = 0; 3179 int64 totalSize = 0; 3180 3181 status_t err = CalcItemsAndSize(&loopControl, list, 0, &totalItems, 3182 &totalSize); 3183 if (err == B_OK) { 3184 loopControl.Init(totalItems, totalItems); 3185 3186 int32 count = list->CountItems(); 3187 for (int32 index = 0; index < count; index++) { 3188 entry_ref ref(*list->ItemAt(index)); 3189 BEntry entry(&ref); 3190 BPath originalPath; 3191 3192 loopControl.UpdateStatus(ref.name, ref, 1, true); 3193 3194 if (FSGetOriginalPath(&entry, &originalPath) != B_OK) 3195 continue; 3196 3197 BEntry originalEntry(originalPath.Path()); 3198 BPath parentPath; 3199 err = originalPath.GetParent(&parentPath); 3200 if (err != B_OK) 3201 continue; 3202 BEntry parentEntry(parentPath.Path()); 3203 3204 if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { 3205 if (FSRecursiveCreateFolder(parentPath) == B_OK) { 3206 originalEntry.SetTo(originalPath.Path()); 3207 if (entry.InitCheck() != B_OK) 3208 continue; 3209 } 3210 } 3211 3212 if (!originalEntry.Exists()) { 3213 BDirectory dir(parentPath.Path()); 3214 if (dir.InitCheck() == B_OK) { 3215 const char* leafName = originalEntry.Name(); 3216 if (entry.MoveTo(&dir, leafName) == B_OK) { 3217 BNode node(&entry); 3218 if (node.InitCheck() == B_OK) 3219 node.RemoveAttr(kAttrOriginalPath); 3220 } 3221 } 3222 } 3223 3224 err = loopControl.CheckUserCanceled(); 3225 if (err != B_OK) 3226 break; 3227 } 3228 } 3229 3230 delete list; 3231 3232 return err; 3233 } 3234 3235 void 3236 FSCreateTrashDirs() 3237 { 3238 BVolume volume; 3239 BVolumeRoster roster; 3240 3241 roster.Rewind(); 3242 while (roster.GetNextVolume(&volume) == B_OK) { 3243 if (volume.IsReadOnly() || !volume.IsPersistent()) 3244 continue; 3245 3246 BDirectory trashDir; 3247 FSGetTrashDir(&trashDir, volume.Device()); 3248 } 3249 } 3250 3251 3252 status_t 3253 FSCreateNewFolder(entry_ref* ref) 3254 { 3255 node_ref node; 3256 node.device = ref->device; 3257 node.node = ref->directory; 3258 3259 BDirectory dir(&node); 3260 status_t result = dir.InitCheck(); 3261 if (result != B_OK) 3262 return result; 3263 3264 // ToDo: is that really necessary here? 3265 BString name(ref->name); 3266 FSMakeOriginalName(name, &dir, " -"); 3267 ref->set_name(name.String()); // update ref in case the folder got renamed 3268 3269 BDirectory newDir; 3270 result = dir.CreateDirectory(name.String(), &newDir); 3271 if (result != B_OK) 3272 return result; 3273 3274 BNodeInfo nodeInfo(&newDir); 3275 nodeInfo.SetType(B_DIR_MIMETYPE); 3276 3277 return result; 3278 } 3279 3280 3281 status_t 3282 FSCreateNewFolderIn(const node_ref* dirNode, entry_ref* newRef, 3283 node_ref* newNode) 3284 { 3285 BDirectory dir(dirNode); 3286 status_t result = dir.InitCheck(); 3287 if (result == B_OK) { 3288 char name[B_FILE_NAME_LENGTH]; 3289 strlcpy(name, B_TRANSLATE("New folder"), sizeof(name)); 3290 3291 int fnum = 1; 3292 while (dir.Contains(name)) { 3293 // if base name already exists then add a number 3294 // TODO: move this logic to FSMakeOriginalName 3295 if (++fnum > 9) 3296 snprintf(name, sizeof(name), B_TRANSLATE("New folder%d"), fnum); 3297 else 3298 snprintf(name, sizeof(name), B_TRANSLATE("New folder %d"), fnum); 3299 } 3300 3301 BDirectory newDir; 3302 result = dir.CreateDirectory(name, &newDir); 3303 if (result == B_OK) { 3304 BEntry entry; 3305 newDir.GetEntry(&entry); 3306 entry.GetRef(newRef); 3307 entry.GetNodeRef(newNode); 3308 3309 BNodeInfo nodeInfo(&newDir); 3310 nodeInfo.SetType(B_DIR_MIMETYPE); 3311 3312 // add undo item 3313 NewFolderUndo undo(*newRef); 3314 return B_OK; 3315 } 3316 } 3317 3318 BAlert* alert = new BAlert("", 3319 B_TRANSLATE("Sorry, could not create a new folder."), 3320 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3321 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3322 alert->Go(); 3323 3324 return result; 3325 } 3326 3327 3328 ReadAttrResult 3329 ReadAttr(const BNode* node, const char* hostAttrName, 3330 const char* foreignAttrName, type_code type, off_t offset, void* buffer, 3331 size_t length, void (*swapFunc)(void*), bool isForeign) 3332 { 3333 if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, 3334 length) == (ssize_t)length) { 3335 return kReadAttrNativeOK; 3336 } 3337 3338 // PRINT(("trying %s\n", foreignAttrName)); 3339 // try the other endianness 3340 if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) 3341 != (ssize_t)length) { 3342 return kReadAttrFailed; 3343 } 3344 3345 // PRINT(("got %s\n", foreignAttrName)); 3346 if (!swapFunc) 3347 return kReadAttrForeignOK; 3348 3349 (swapFunc)(buffer); 3350 // run the endian swapper 3351 3352 return kReadAttrForeignOK; 3353 } 3354 3355 3356 ReadAttrResult 3357 GetAttrInfo(const BNode* node, const char* hostAttrName, 3358 const char* foreignAttrName, type_code* type, size_t* size) 3359 { 3360 attr_info info; 3361 3362 if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { 3363 if (type) 3364 *type = info.type; 3365 if (size) 3366 *size = (size_t)info.size; 3367 3368 return kReadAttrNativeOK; 3369 } 3370 3371 if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { 3372 if (type) 3373 *type = info.type; 3374 if (size) 3375 *size = (size_t)info.size; 3376 3377 return kReadAttrForeignOK; 3378 } 3379 return kReadAttrFailed; 3380 } 3381 3382 3383 status_t 3384 FSGetParentVirtualDirectoryAware(const BEntry& entry, entry_ref& _ref) 3385 { 3386 node_ref nodeRef; 3387 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3388 if (VirtualDirectoryManager* manager 3389 = VirtualDirectoryManager::Instance()) { 3390 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3391 if (manager->GetParentDirectoryDefinitionFile(nodeRef, _ref, 3392 nodeRef)) { 3393 return B_OK; 3394 } 3395 } 3396 } 3397 3398 status_t error; 3399 BDirectory parent; 3400 BEntry parentEntry; 3401 if ((error = entry.GetParent(&parent)) != B_OK 3402 || (error = parent.GetEntry(&parentEntry)) != B_OK 3403 || (error = parentEntry.GetRef(&_ref)) != B_OK) { 3404 return error; 3405 } 3406 3407 return B_OK; 3408 } 3409 3410 3411 status_t 3412 FSGetParentVirtualDirectoryAware(const BEntry& entry, BEntry& _entry) 3413 { 3414 node_ref nodeRef; 3415 if (entry.GetNodeRef(&nodeRef) == B_OK) { 3416 if (VirtualDirectoryManager* manager 3417 = VirtualDirectoryManager::Instance()) { 3418 AutoLocker<VirtualDirectoryManager> managerLocker(manager); 3419 entry_ref parentRef; 3420 if (manager->GetParentDirectoryDefinitionFile(nodeRef, parentRef, 3421 nodeRef)) { 3422 return _entry.SetTo(&parentRef); 3423 } 3424 } 3425 } 3426 3427 return entry.GetParent(&_entry); 3428 } 3429 3430 3431 status_t 3432 FSGetParentVirtualDirectoryAware(const BEntry& entry, BNode& _node) 3433 { 3434 entry_ref ref; 3435 status_t error = FSGetParentVirtualDirectoryAware(entry, ref); 3436 if (error == B_OK) 3437 error = _node.SetTo(&ref); 3438 3439 return error; 3440 } 3441 3442 3443 // launching code 3444 3445 static status_t 3446 TrackerOpenWith(const BMessage* refs) 3447 { 3448 BMessage clone(*refs); 3449 3450 ASSERT(dynamic_cast<TTracker*>(be_app) != NULL); 3451 ASSERT(clone.what != 0); 3452 3453 clone.AddInt32("launchUsingSelector", 0); 3454 // runs the Open With window 3455 be_app->PostMessage(&clone); 3456 3457 return B_OK; 3458 } 3459 3460 3461 static void 3462 AsynchLaunchBinder(void (*func)(const entry_ref*, const BMessage*, bool on), 3463 const entry_ref* appRef, const BMessage* refs, bool openWithOK) 3464 { 3465 BMessage* task = new BMessage; 3466 task->AddPointer("function", (void*)func); 3467 task->AddMessage("refs", refs); 3468 task->AddBool("openWithOK", openWithOK); 3469 if (appRef != NULL) 3470 task->AddRef("appRef", appRef); 3471 3472 extern BLooper* gLaunchLooper; 3473 gLaunchLooper->PostMessage(task); 3474 } 3475 3476 3477 static bool 3478 SniffIfGeneric(const entry_ref* ref) 3479 { 3480 BNode node(ref); 3481 char type[B_MIME_TYPE_LENGTH]; 3482 BNodeInfo info(&node); 3483 if (info.GetType(type) == B_OK 3484 && strcasecmp(type, B_FILE_MIME_TYPE) != 0) { 3485 // already has a type and it's not octet stream 3486 return false; 3487 } 3488 3489 BPath path(ref); 3490 if (path.Path()) { 3491 // force a mimeset 3492 node.RemoveAttr(kAttrMIMEType); 3493 update_mime_info(path.Path(), 0, 1, 1); 3494 } 3495 3496 return true; 3497 } 3498 3499 3500 static void 3501 SniffIfGeneric(const BMessage* refs) 3502 { 3503 entry_ref ref; 3504 for (int32 index = 0; ; index++) { 3505 if (refs->FindRef("refs", index, &ref) != B_OK) 3506 break; 3507 SniffIfGeneric(&ref); 3508 } 3509 } 3510 3511 3512 static void 3513 _TrackerLaunchAppWithDocuments(const entry_ref* appRef, const BMessage* refs, 3514 bool openWithOK) 3515 { 3516 team_id team; 3517 3518 status_t error = B_ERROR; 3519 BString alertString; 3520 3521 for (int32 mimesetIt = 0; ; mimesetIt++) { 3522 error = be_roster->Launch(appRef, refs, &team); 3523 if (error == B_ALREADY_RUNNING) 3524 // app already running, not really an error 3525 error = B_OK; 3526 3527 if (error == B_OK) 3528 break; 3529 3530 if (mimesetIt > 0) 3531 break; 3532 3533 // failed to open, try mimesetting the refs and launching again 3534 SniffIfGeneric(refs); 3535 } 3536 3537 if (error == B_OK) { 3538 // close possible parent window, if specified 3539 const node_ref* nodeToClose = 0; 3540 ssize_t numBytes; 3541 if (refs != NULL && refs->FindData("nodeRefsToClose", B_RAW_TYPE, 3542 (const void**)&nodeToClose, &numBytes) == B_OK 3543 && nodeToClose != NULL) { 3544 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 3545 if (tracker != NULL) 3546 tracker->CloseParent(*nodeToClose); 3547 } 3548 } else { 3549 alertString.SetTo(B_TRANSLATE("Could not open \"%name\" (%error). ")); 3550 alertString.ReplaceFirst("%name", appRef->name); 3551 alertString.ReplaceFirst("%error", strerror(error)); 3552 if (refs != NULL && openWithOK && error != B_SHUTTING_DOWN) { 3553 alertString << B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3554 BAlert* alert = new BAlert("", alertString.String(), 3555 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3556 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3557 alert->SetShortcut(0, B_ESCAPE); 3558 if (alert->Go() == 1) 3559 error = TrackerOpenWith(refs); 3560 } else { 3561 BAlert* alert = new BAlert("", alertString.String(), 3562 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3563 B_WARNING_ALERT); 3564 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3565 alert->Go(); 3566 } 3567 } 3568 } 3569 3570 3571 extern "C" char** environ; 3572 3573 3574 static status_t 3575 LoaderErrorDetails(const entry_ref* app, BString &details) 3576 { 3577 BPath path; 3578 BEntry appEntry(app, true); 3579 3580 status_t result = appEntry.GetPath(&path); 3581 if (result != B_OK) 3582 return result; 3583 3584 char* argv[2] = { const_cast<char*>(path.Path()), 0}; 3585 3586 port_id errorPort = create_port(1, "Tracker loader error"); 3587 3588 // count environment variables 3589 int32 envCount = 0; 3590 while (environ[envCount] != NULL) 3591 envCount++; 3592 3593 char** flatArgs = NULL; 3594 size_t flatArgsSize; 3595 result = __flatten_process_args((const char**)argv, 1, 3596 environ, &envCount, argv[0], &flatArgs, &flatArgsSize); 3597 if (result != B_OK) 3598 return result; 3599 3600 result = _kern_load_image(flatArgs, flatArgsSize, 1, envCount, 3601 B_NORMAL_PRIORITY, B_WAIT_TILL_LOADED, errorPort, 0); 3602 if (result == B_OK) { 3603 // we weren't supposed to be able to start the application... 3604 return B_ERROR; 3605 } 3606 3607 // read error message from port and construct details string 3608 3609 ssize_t bufferSize; 3610 3611 do { 3612 bufferSize = port_buffer_size_etc(errorPort, B_RELATIVE_TIMEOUT, 0); 3613 } while (bufferSize == B_INTERRUPTED); 3614 3615 if (bufferSize <= B_OK) { 3616 delete_port(errorPort); 3617 return bufferSize; 3618 } 3619 3620 uint8* buffer = (uint8*)malloc(bufferSize); 3621 if (buffer == NULL) { 3622 delete_port(errorPort); 3623 return B_NO_MEMORY; 3624 } 3625 3626 bufferSize = read_port_etc(errorPort, NULL, buffer, bufferSize, 3627 B_RELATIVE_TIMEOUT, 0); 3628 delete_port(errorPort); 3629 3630 if (bufferSize < B_OK) { 3631 free(buffer); 3632 return bufferSize; 3633 } 3634 3635 BMessage message; 3636 result = message.Unflatten((const char*)buffer); 3637 free(buffer); 3638 3639 if (result != B_OK) 3640 return result; 3641 3642 int32 errorCode = B_ERROR; 3643 result = message.FindInt32("error", &errorCode); 3644 if (result != B_OK) 3645 return result; 3646 3647 const char* detailName = NULL; 3648 switch (errorCode) { 3649 case B_MISSING_LIBRARY: 3650 detailName = "missing library"; 3651 break; 3652 3653 case B_MISSING_SYMBOL: 3654 detailName = "missing symbol"; 3655 break; 3656 } 3657 3658 if (detailName == NULL) 3659 return B_ERROR; 3660 3661 const char* detail; 3662 for (int32 i = 0; message.FindString(detailName, i, &detail) == B_OK; 3663 i++) { 3664 if (i > 0) 3665 details += ", "; 3666 details += detail; 3667 } 3668 3669 return B_OK; 3670 } 3671 3672 3673 static void 3674 _TrackerLaunchDocuments(const entry_ref*, const BMessage* refs, 3675 bool openWithOK) 3676 { 3677 if (refs == NULL) 3678 return; 3679 3680 BMessage copyOfRefs(*refs); 3681 3682 entry_ref documentRef; 3683 if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) { 3684 // nothing to launch, we are done 3685 return; 3686 } 3687 3688 status_t error = B_ERROR; 3689 entry_ref app; 3690 BMessage* refsToPass = NULL; 3691 BString alertString; 3692 const char* alternative = 0; 3693 3694 for (int32 mimesetIt = 0; ; mimesetIt++) { 3695 alertString = ""; 3696 error = be_roster->FindApp(&documentRef, &app); 3697 3698 if (error != B_OK && mimesetIt == 0) { 3699 SniffIfGeneric(©OfRefs); 3700 continue; 3701 } 3702 3703 if (error != B_OK) { 3704 alertString.SetTo(B_TRANSLATE("Could not find an application to " 3705 "open \"%name\" (%error). ")); 3706 alertString.ReplaceFirst("%name", documentRef.name); 3707 alertString.ReplaceFirst("%error", strerror(error)); 3708 if (openWithOK) 3709 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3710 3711 break; 3712 } else { 3713 BEntry appEntry(&app, true); 3714 for (int32 index = 0;;) { 3715 // remove the app itself from the refs received so we don't 3716 // try to open ourselves 3717 entry_ref ref; 3718 if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) 3719 break; 3720 3721 // deal with symlinks properly 3722 BEntry documentEntry(&ref, true); 3723 if (appEntry == documentEntry) { 3724 PRINT(("stripping %s, app %s \n", ref.name, app.name)); 3725 copyOfRefs.RemoveData("refs", index); 3726 } else { 3727 PRINT(("leaving %s, app %s \n", ref.name, app.name)); 3728 index++; 3729 } 3730 } 3731 3732 refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; 3733 team_id team; 3734 error = be_roster->Launch(&app, refsToPass, &team); 3735 if (error == B_ALREADY_RUNNING) 3736 // app already running, not really an error 3737 error = B_OK; 3738 if (error == B_OK || mimesetIt != 0) 3739 break; 3740 if (error == B_LAUNCH_FAILED_EXECUTABLE) { 3741 BVolume volume(documentRef.device); 3742 if (volume.IsReadOnly()) { 3743 BMimeType type; 3744 error = BMimeType::GuessMimeType(&documentRef, &type); 3745 if (error != B_OK) 3746 break; 3747 error = be_roster->FindApp(type.Type(), &app); 3748 if (error != B_OK) 3749 break; 3750 error = be_roster->Launch(&app, refs, &team); 3751 if (error == B_ALREADY_RUNNING) 3752 // app already running, not really an error 3753 error = B_OK; 3754 if (error == B_OK || mimesetIt != 0) 3755 break; 3756 } 3757 } 3758 3759 SniffIfGeneric(©OfRefs); 3760 } 3761 } 3762 3763 if (error != B_OK && alertString.Length() == 0) { 3764 BString loaderErrorString; 3765 bool openedDocuments = true; 3766 3767 if (!refsToPass) { 3768 // we just double clicked the app itself, do not offer to 3769 // find a handling app 3770 openWithOK = false; 3771 openedDocuments = false; 3772 } 3773 if (error == B_UNKNOWN_EXECUTABLE && !refsToPass) { 3774 // We know it's an executable, but something unsupported 3775 alertString.SetTo(B_TRANSLATE("\"%name\" is an unsupported " 3776 "executable.")); 3777 alertString.ReplaceFirst("%name", app.name); 3778 } else if (error == B_LEGACY_EXECUTABLE && !refsToPass) { 3779 // For the moment, this marks an old R3 binary, we may want to 3780 // extend it to gcc2 binaries someday post R1 3781 alertString.SetTo(B_TRANSLATE("\"%name\" is a legacy executable. " 3782 "Please obtain an updated version or recompile " 3783 "the application.")); 3784 alertString.ReplaceFirst("%name", app.name); 3785 } else if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { 3786 alertString.SetTo(B_TRANSLATE("Could not open \"%name\". " 3787 "The file is mistakenly marked as executable. ")); 3788 alertString.ReplaceFirst("%name", app.name); 3789 3790 if (!openWithOK) { 3791 // offer the possibility to change the permissions 3792 3793 alertString << B_TRANSLATE("\nShould this be fixed?"); 3794 BAlert* alert = new BAlert("", alertString.String(), 3795 B_TRANSLATE("Cancel"), B_TRANSLATE("Proceed"), 0, 3796 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3797 alert->SetShortcut(0, B_ESCAPE); 3798 if (alert->Go() == 1) { 3799 BEntry entry(&documentRef); 3800 mode_t permissions; 3801 3802 error = entry.GetPermissions(&permissions); 3803 if (error == B_OK) { 3804 error = entry.SetPermissions(permissions 3805 & ~(S_IXUSR | S_IXGRP | S_IXOTH)); 3806 } 3807 if (error == B_OK) { 3808 // we updated the permissions, so let's try again 3809 _TrackerLaunchDocuments(NULL, refs, false); 3810 return; 3811 } else { 3812 alertString.SetTo(B_TRANSLATE("Could not update " 3813 "permissions of file \"%name\". %error")); 3814 alertString.ReplaceFirst("%name", app.name); 3815 alertString.ReplaceFirst("%error", strerror(error)); 3816 } 3817 } else 3818 return; 3819 } 3820 3821 alternative = B_TRANSLATE_NOCOLLECT(kFindApplicationStr); 3822 } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { 3823 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3824 "because application \"%app\" is in the Trash. ")); 3825 alertString.ReplaceFirst("%document", documentRef.name); 3826 alertString.ReplaceFirst("%app", app.name); 3827 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3828 } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { 3829 alertString.SetTo( 3830 B_TRANSLATE("Could not open \"%name\" (%error). ")); 3831 alertString.ReplaceFirst("%name", documentRef.name); 3832 alertString.ReplaceFirst("%error", strerror(error)); 3833 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3834 } else if (error == B_MISSING_SYMBOL 3835 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3836 if (openedDocuments) { 3837 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3838 "with application \"%app\" (Missing symbol: %symbol). " 3839 "\n")); 3840 alertString.ReplaceFirst("%document", documentRef.name); 3841 alertString.ReplaceFirst("%app", app.name); 3842 alertString.ReplaceFirst("%symbol", 3843 loaderErrorString.String()); 3844 } else { 3845 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3846 "(Missing symbol: %symbol). \n")); 3847 alertString.ReplaceFirst("%document", documentRef.name); 3848 alertString.ReplaceFirst("%symbol", 3849 loaderErrorString.String()); 3850 } 3851 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3852 } else if (error == B_MISSING_LIBRARY 3853 && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { 3854 if (openedDocuments) { 3855 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3856 "with application \"%app\" (Missing libraries: %library). " 3857 "\n")); 3858 alertString.ReplaceFirst("%document", documentRef.name); 3859 alertString.ReplaceFirst("%app", app.name); 3860 alertString.ReplaceFirst("%library", 3861 loaderErrorString.String()); 3862 } else { 3863 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" " 3864 "(Missing libraries: %library). \n")); 3865 alertString.ReplaceFirst("%document", documentRef.name); 3866 alertString.ReplaceFirst("%library", 3867 loaderErrorString.String()); 3868 } 3869 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3870 } else { 3871 alertString.SetTo(B_TRANSLATE("Could not open \"%document\" with " 3872 "application \"%app\" (%error). ")); 3873 alertString.ReplaceFirst("%document", documentRef.name); 3874 alertString.ReplaceFirst("%app", app.name); 3875 alertString.ReplaceFirst("%error", strerror(error)); 3876 alternative = B_TRANSLATE_NOCOLLECT(kFindAlternativeStr); 3877 } 3878 } 3879 3880 if (error != B_OK) { 3881 if (openWithOK) { 3882 ASSERT(alternative); 3883 alertString << alternative; 3884 BAlert* alert = new BAlert("", alertString.String(), 3885 B_TRANSLATE("Cancel"), B_TRANSLATE("Find"), 0, 3886 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3887 alert->SetShortcut(0, B_ESCAPE); 3888 if (alert->Go() == 1) 3889 error = TrackerOpenWith(refs); 3890 } else { 3891 BAlert* alert = new BAlert("", alertString.String(), 3892 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, 3893 B_WARNING_ALERT); 3894 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3895 alert->Go(); 3896 } 3897 } 3898 } 3899 3900 // the following three calls don't return any reasonable error codes, 3901 // should fix that, making them void 3902 3903 status_t 3904 TrackerLaunch(const entry_ref* appRef, const BMessage* refs, bool async, 3905 bool openWithOK) 3906 { 3907 if (!async) 3908 _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); 3909 else { 3910 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, 3911 openWithOK); 3912 } 3913 3914 return B_OK; 3915 } 3916 3917 status_t 3918 TrackerLaunch(const entry_ref* appRef, bool async) 3919 { 3920 if (!async) 3921 _TrackerLaunchAppWithDocuments(appRef, NULL, false); 3922 else 3923 AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); 3924 3925 return B_OK; 3926 } 3927 3928 status_t 3929 TrackerLaunch(const BMessage* refs, bool async, bool openWithOK) 3930 { 3931 if (!async) 3932 _TrackerLaunchDocuments(NULL, refs, openWithOK); 3933 else 3934 AsynchLaunchBinder(&_TrackerLaunchDocuments, NULL, refs, openWithOK); 3935 3936 return B_OK; 3937 } 3938 3939 3940 // external launch calls; need to be robust, work if Tracker is not running 3941 3942 3943 #if !B_BEOS_VERSION_DANO 3944 _IMPEXP_TRACKER 3945 #endif 3946 status_t 3947 FSLaunchItem(const entry_ref* application, const BMessage* refsReceived, 3948 bool async, bool openWithOK) 3949 { 3950 return TrackerLaunch(application, refsReceived, async, openWithOK); 3951 } 3952 3953 3954 #if !B_BEOS_VERSION_DANO 3955 _IMPEXP_TRACKER 3956 #endif 3957 status_t 3958 FSOpenWith(BMessage* listOfRefs) 3959 { 3960 status_t result = B_ERROR; 3961 listOfRefs->what = B_REFS_RECEIVED; 3962 3963 if (dynamic_cast<TTracker*>(be_app) != NULL) 3964 result = TrackerOpenWith(listOfRefs); 3965 else 3966 ASSERT(!"not yet implemented"); 3967 3968 return result; 3969 } 3970 3971 3972 // legacy calls, need for compatibility 3973 3974 3975 void 3976 FSOpenWithDocuments(const entry_ref* executable, BMessage* documents) 3977 { 3978 TrackerLaunch(executable, documents, true); 3979 delete documents; 3980 } 3981 3982 3983 status_t 3984 FSLaunchUsing(const entry_ref* ref, BMessage* listOfRefs) 3985 { 3986 BMessage temp(B_REFS_RECEIVED); 3987 if (listOfRefs == NULL) { 3988 ASSERT(ref != NULL); 3989 temp.AddRef("refs", ref); 3990 listOfRefs = &temp; 3991 } 3992 FSOpenWith(listOfRefs); 3993 3994 return B_OK; 3995 } 3996 3997 3998 status_t 3999 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32, bool async) 4000 { 4001 if (refs != NULL) 4002 refs->what = B_REFS_RECEIVED; 4003 4004 status_t result = TrackerLaunch(appRef, refs, async, true); 4005 delete refs; 4006 4007 return result; 4008 } 4009 4010 4011 void 4012 FSLaunchItem(const entry_ref* appRef, BMessage* refs, int32 workspace) 4013 { 4014 FSLaunchItem(appRef, refs, workspace, true); 4015 } 4016 4017 4018 // Get the original path of an entry in the trash 4019 status_t 4020 FSGetOriginalPath(BEntry* entry, BPath* result) 4021 { 4022 status_t err; 4023 entry_ref ref; 4024 err = entry->GetRef(&ref); 4025 if (err != B_OK) 4026 return err; 4027 4028 // Only call the routine for entries in the trash 4029 if (!FSInTrashDir(&ref)) 4030 return B_ERROR; 4031 4032 BNode node(entry); 4033 BString originalPath; 4034 if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { 4035 // We're in luck, the entry has the original path in an attribute 4036 err = result->SetTo(originalPath.String()); 4037 return err; 4038 } 4039 4040 // Iterate the parent directories to find one with 4041 // the original path attribute 4042 BEntry parent(*entry); 4043 err = parent.InitCheck(); 4044 if (err != B_OK) 4045 return err; 4046 4047 // walk up the directory structure until we find a node 4048 // with original path attribute 4049 do { 4050 // move to the parent of this node 4051 err = parent.GetParent(&parent); 4052 if (err != B_OK) 4053 return err; 4054 4055 // return if we are at the root of the trash 4056 if (FSIsTrashDir(&parent)) 4057 return B_ENTRY_NOT_FOUND; 4058 4059 // get the parent as a node 4060 err = node.SetTo(&parent); 4061 if (err != B_OK) 4062 return err; 4063 } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); 4064 4065 // Found the attribute, figure out there this file 4066 // used to live, based on the successfully-read attribute 4067 err = result->SetTo(originalPath.String()); 4068 if (err != B_OK) 4069 return err; 4070 4071 BPath path, pathParent; 4072 err = parent.GetPath(&pathParent); 4073 if (err != B_OK) 4074 return err; 4075 4076 err = entry->GetPath(&path); 4077 if (err != B_OK) 4078 return err; 4079 4080 result->Append(path.Path() + strlen(pathParent.Path()) + 1); 4081 // compute the new path by appending the offset of 4082 // the item we are locating, to the original path 4083 // of the parent 4084 4085 return B_OK; 4086 } 4087 4088 4089 directory_which 4090 WellKnowEntryList::Match(const node_ref* node) 4091 { 4092 const WellKnownEntry* result = MatchEntry(node); 4093 if (result != NULL) 4094 return result->which; 4095 4096 return (directory_which)-1; 4097 } 4098 4099 4100 const WellKnowEntryList::WellKnownEntry* 4101 WellKnowEntryList::MatchEntry(const node_ref* node) 4102 { 4103 if (self == NULL) 4104 self = new WellKnowEntryList(); 4105 4106 return self->MatchEntryCommon(node); 4107 } 4108 4109 4110 const WellKnowEntryList::WellKnownEntry* 4111 WellKnowEntryList::MatchEntryCommon(const node_ref* node) 4112 { 4113 uint32 count = entries.size(); 4114 for (uint32 index = 0; index < count; index++) { 4115 if (*node == entries[index].node) 4116 return &entries[index]; 4117 } 4118 4119 return NULL; 4120 } 4121 4122 4123 void 4124 WellKnowEntryList::Quit() 4125 { 4126 delete self; 4127 self = NULL; 4128 } 4129 4130 4131 void 4132 WellKnowEntryList::AddOne(directory_which which, const char* name) 4133 { 4134 BPath path; 4135 if (find_directory(which, &path, true) != B_OK) 4136 return; 4137 4138 BEntry entry(path.Path(), true); 4139 node_ref node; 4140 if (entry.GetNodeRef(&node) != B_OK) 4141 return; 4142 4143 entries.push_back(WellKnownEntry(&node, which, name)); 4144 } 4145 4146 4147 void 4148 WellKnowEntryList::AddOne(directory_which which, directory_which base, 4149 const char* extra, const char* name) 4150 { 4151 BPath path; 4152 if (find_directory(base, &path, true) != B_OK) 4153 return; 4154 4155 path.Append(extra); 4156 BEntry entry(path.Path(), true); 4157 node_ref node; 4158 if (entry.GetNodeRef(&node) != B_OK) 4159 return; 4160 4161 entries.push_back(WellKnownEntry(&node, which, name)); 4162 } 4163 4164 4165 void 4166 WellKnowEntryList::AddOne(directory_which which, const char* path, 4167 const char* name) 4168 { 4169 BEntry entry(path, true); 4170 node_ref node; 4171 if (entry.GetNodeRef(&node) != B_OK) 4172 return; 4173 4174 entries.push_back(WellKnownEntry(&node, which, name)); 4175 } 4176 4177 4178 WellKnowEntryList::WellKnowEntryList() 4179 { 4180 AddOne(B_SYSTEM_DIRECTORY, "system"); 4181 AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); 4182 AddOne(B_USER_DIRECTORY, "home"); 4183 4184 AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); 4185 AddOne(B_USER_FONTS_DIRECTORY, "fonts"); 4186 4187 AddOne(B_BEOS_APPS_DIRECTORY, "apps"); 4188 AddOne(B_APPS_DIRECTORY, "apps"); 4189 AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, 4190 B_USER_DESKBAR_DIRECTORY, "Applications", "apps"); 4191 4192 AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); 4193 AddOne(B_PREFERENCES_DIRECTORY, "preferences"); 4194 AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, 4195 B_USER_DESKBAR_DIRECTORY, "Preferences", "preferences"); 4196 4197 AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", 4198 "mail"); 4199 4200 AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, 4201 "queries", "queries"); 4202 4203 AddOne(B_SYSTEM_DEVELOP_DIRECTORY, "develop"); 4204 AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, 4205 B_USER_DESKBAR_DIRECTORY, "Development", "develop"); 4206 4207 AddOne(B_USER_CONFIG_DIRECTORY, "config"); 4208 4209 AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, 4210 "people", "people"); 4211 4212 AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, 4213 "downloads", "downloads"); 4214 } 4215 4216 WellKnowEntryList* WellKnowEntryList::self = NULL; 4217 4218 } // namespace BPrivate 4219